@jbrowse/core 5.0.0-beta.4 → 5.0.0-beta.7

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 (110) hide show
  1. package/README.md +278 -250
  2. package/esm/PluginManager.js +1 -1
  3. package/esm/ReExports/publicUi.d.ts +1 -1
  4. package/esm/ReExports/publicUi.js +1 -1
  5. package/esm/ReExports/publicUtil.d.ts +1 -1
  6. package/esm/ReExports/publicUtil.js +1 -1
  7. package/esm/ReExports/workerNamespaceNames.js +3 -9
  8. package/esm/assemblyManager/assemblyConfigSchema.d.ts +2 -1
  9. package/esm/assemblyManager/assemblyConfigSchema.js +2 -1
  10. package/esm/assemblyManager/expandAssemblyConfigShorthand.d.ts +15 -15
  11. package/esm/assemblyManager/expandAssemblyConfigShorthand.js +34 -18
  12. package/esm/configuration/configurationSchema.d.ts +1 -2
  13. package/esm/configuration/configurationSchema.js +3 -8
  14. package/esm/configuration/configurationSlot.d.ts +0 -47
  15. package/esm/configuration/configurationSlot.js +4 -54
  16. package/esm/configuration/fillLocations.d.ts +14 -0
  17. package/esm/configuration/fillLocations.js +14 -0
  18. package/esm/configuration/fullConfSnapshot.d.ts +0 -15
  19. package/esm/configuration/fullConfSnapshot.js +0 -28
  20. package/esm/configuration/getConf.d.ts +4 -34
  21. package/esm/configuration/getConf.js +3 -38
  22. package/esm/configuration/index.d.ts +3 -4
  23. package/esm/configuration/index.js +3 -2
  24. package/esm/configuration/schemaRegistry.d.ts +1 -2
  25. package/esm/configuration/schemaRegistry.js +1 -2
  26. package/esm/configuration/slotFacade.d.ts +1 -16
  27. package/esm/configuration/slotFacade.js +1 -2
  28. package/esm/configuration/tabixShorthand.js +3 -3
  29. package/esm/configuration/types.d.ts +4 -16
  30. package/esm/data_adapters/CytobandAdapter/configSchema.js +3 -4
  31. package/esm/pluggableElementTypes/DisplayType.d.ts +5 -3
  32. package/esm/pluggableElementTypes/DisplayType.js +3 -1
  33. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +30 -0
  34. package/esm/pluggableElementTypes/models/BaseTrackModel.js +45 -17
  35. package/esm/rpc/RpcRegistry.d.ts +7 -1
  36. package/esm/rpc/coreRpcMethods.d.ts +1 -0
  37. package/esm/rpc/coreRpcMethods.js +1 -0
  38. package/esm/rpc/methods/CoreEncodeFeatures.d.ts +15 -0
  39. package/esm/rpc/methods/CoreEncodeFeatures.js +62 -0
  40. package/esm/svg/svgId.d.ts +1 -1
  41. package/esm/svg/svgId.js +1 -1
  42. package/esm/ui/LegendSwatchGlyph.js +26 -21
  43. package/esm/ui/MenuTypes.d.ts +5 -52
  44. package/esm/ui/SizeSliderRow.d.ts +1 -3
  45. package/esm/ui/SizeSliderRow.js +4 -6
  46. package/esm/ui/SvgColorLegend.d.ts +9 -3
  47. package/esm/ui/SvgColorLegend.js +74 -33
  48. package/esm/ui/colorScale.d.ts +64 -0
  49. package/esm/ui/colorScale.js +41 -0
  50. package/esm/ui/colors.d.ts +11 -0
  51. package/esm/ui/colors.js +7 -0
  52. package/esm/ui/index.d.ts +5 -7
  53. package/esm/ui/index.js +3 -13
  54. package/esm/ui/legendMenuItem.d.ts +5 -16
  55. package/esm/ui/legendMenuItem.js +3 -14
  56. package/esm/ui/legendSpec.d.ts +11 -5
  57. package/esm/ui/legendSpec.js +8 -11
  58. package/esm/ui/makeSizeMenu.d.ts +0 -6
  59. package/esm/ui/makeSizeMenu.js +6 -30
  60. package/esm/ui/measureLegendText.js +1 -1
  61. package/esm/ui/menuItemAdornment.d.ts +9 -19
  62. package/esm/ui/menuItemAdornment.js +10 -31
  63. package/esm/ui/menuItems.d.ts +2 -6
  64. package/esm/ui/menuItems.js +0 -4
  65. package/esm/ui/palette.js +2 -1
  66. package/esm/ui/toggleMenuItems.d.ts +6 -13
  67. package/esm/ui/toggleMenuItems.js +13 -29
  68. package/esm/util/colorRamp.d.ts +3 -3
  69. package/esm/util/colorRamp.js +2 -2
  70. package/esm/util/featureTransforms.d.ts +15 -0
  71. package/esm/util/featureTransforms.js +342 -0
  72. package/esm/util/freezeDeep.d.ts +8 -10
  73. package/esm/util/freezeDeep.js +8 -10
  74. package/esm/util/glyphNames.d.ts +8 -0
  75. package/esm/util/glyphNames.js +12 -0
  76. package/esm/util/index.d.ts +0 -1
  77. package/esm/util/index.js +0 -1
  78. package/esm/util/markEncoding.d.ts +76 -0
  79. package/esm/util/markEncoding.js +436 -0
  80. package/esm/util/markEncodingTypes.d.ts +331 -0
  81. package/esm/util/markEncodingTypes.js +8 -0
  82. package/esm/util/renderToStaticMarkup.js +1 -1
  83. package/esm/util/starFusion.d.ts +26 -0
  84. package/esm/util/starFusion.js +37 -0
  85. package/esm/util/trackConfigDelta.d.ts +4 -8
  86. package/esm/util/trackConfigDelta.js +2 -4
  87. package/esm/util/tracks.d.ts +7 -1
  88. package/esm/util/tracks.js +72 -7
  89. package/esm/util/types/index.d.ts +12 -4
  90. package/esm/util/whenViewSettled.d.ts +9 -0
  91. package/esm/util/whenViewSettled.js +12 -4
  92. package/package.json +13 -3
  93. package/esm/configuration/promotableDefaults.d.ts +0 -234
  94. package/esm/configuration/promotableDefaults.js +0 -395
  95. package/esm/configuration/promotablePin.d.ts +0 -65
  96. package/esm/configuration/promotablePin.js +0 -1
  97. package/esm/configuration/promotableResolve.d.ts +0 -108
  98. package/esm/configuration/promotableResolve.js +0 -117
  99. package/esm/configuration/promotableSlots.d.ts +0 -12
  100. package/esm/configuration/promotableSlots.js +0 -45
  101. package/esm/configuration/slotShape.d.ts +0 -12
  102. package/esm/configuration/slotShape.js +0 -84
  103. package/esm/ui/PinAdornment.d.ts +0 -5
  104. package/esm/ui/PinAdornment.js +0 -67
  105. package/esm/ui/SvgGradientLegend.d.ts +0 -26
  106. package/esm/ui/SvgGradientLegend.js +0 -26
  107. package/esm/ui/promotablePinCoverage.d.ts +0 -58
  108. package/esm/ui/promotablePinCoverage.js +0 -124
  109. package/esm/util/openDisplays.d.ts +0 -23
  110. package/esm/util/openDisplays.js +0 -79
@@ -0,0 +1,436 @@
1
+ import { GLYPH_DISC } from '@jbrowse/render-core/shaders/pointMarkConsts';
2
+ import { categoricalPalette, categoricalValueColor } from '../ui/colors.js';
3
+ import { cssColorToABGR, cssColorToRgba, packAbgr } from './colorBits.js';
4
+ import { VIRIDIS_STOPS, buildColorRampLut } from './colorRamp.js';
5
+ import Flatbush from './flatbush/index.js';
6
+ import { GLYPH_CODES, GLYPH_NAMES } from './glyphNames.js';
7
+ import { isJexl, stringToJexlExpression } from './jexlStrings.js';
8
+ import { buildJexlContext } from './simpleFeature.js';
9
+ export const DEFAULT_MARK_COLOR = '#0068d1';
10
+ // What a feature paints when its colour field is missing or a `jexl:` colour
11
+ // yields a non-string: visible, so a misconfiguration surfaces rather than
12
+ // vanishing.
13
+ const FALLBACK_COLOR = cssColorToABGR('#808080');
14
+ /**
15
+ * #api
16
+ * The key row a feature with nothing in a categorical field lands on, so the
17
+ * legend says why a mark is grey, or a disc, rather than listing a blank
18
+ * value.
19
+ */
20
+ export const NO_VALUE_LABEL = '(no value)';
21
+ function jexlExpression(ref, jexl) {
22
+ if (!jexl) {
23
+ throw new Error(`a jexl: channel needs a jexl instance (${ref})`);
24
+ }
25
+ return stringToJexlExpression(ref, jexl);
26
+ }
27
+ function fieldReader(ref, jexl) {
28
+ if (typeof ref === 'function') {
29
+ return ref;
30
+ }
31
+ if (isJexl(ref)) {
32
+ const expr = jexlExpression(ref, jexl);
33
+ return feature => expr.eval(buildJexlContext({ feature }));
34
+ }
35
+ return feature => feature.get(ref);
36
+ }
37
+ /**
38
+ * #api
39
+ * The field a `y` declaration reads, whichever of its two forms it is
40
+ * written in.
41
+ */
42
+ export function valueField(y) {
43
+ return typeof y === 'string' ? y : y.field;
44
+ }
45
+ function isGlyphName(glyph) {
46
+ return glyph in GLYPH_CODES;
47
+ }
48
+ function glyphReader(glyph, jexl) {
49
+ if (glyph === undefined) {
50
+ return () => GLYPH_DISC;
51
+ }
52
+ if (typeof glyph === 'function') {
53
+ return glyph;
54
+ }
55
+ if (isGlyphName(glyph)) {
56
+ const code = GLYPH_CODES[glyph];
57
+ return () => code;
58
+ }
59
+ const expr = jexlExpression(glyph, jexl);
60
+ return feature => {
61
+ const v = expr.eval(buildJexlContext({ feature }));
62
+ return typeof v === 'string' && isGlyphName(v) ? GLYPH_CODES[v] : GLYPH_DISC;
63
+ };
64
+ }
65
+ function rampStops(ramp) {
66
+ if (ramp === undefined || ramp === 'viridis') {
67
+ return VIRIDIS_STOPS;
68
+ }
69
+ return ramp.map(c => cssColorToRgba(c));
70
+ }
71
+ function lutColorAt(lut, t) {
72
+ const entries = lut.length / 4;
73
+ const i = Math.min(entries - 1, Math.max(0, Math.round(t * (entries - 1))));
74
+ const o = i * 4;
75
+ return packAbgr(lut[o], lut[o + 1], lut[o + 2], lut[o + 3]);
76
+ }
77
+ // The `[0, 1]` fraction a value sits at in the domain: the scale half of the
78
+ // y-channel split (ADR-097), spelled once here for colour. The log arm floors
79
+ // at the domain's own minimum so a domain below 1 still spreads.
80
+ function normalizer(scale, domain) {
81
+ const [min, max] = domain;
82
+ if (scale === 'log') {
83
+ const floorV = min > 0 ? min : 1;
84
+ const logMin = Math.log2(floorV);
85
+ const logRange = Math.log2(Math.max(max, floorV)) - logMin;
86
+ return (v) => logRange <= 0
87
+ ? 0
88
+ : Math.min(1, Math.max(0, (Math.log2(Math.max(v, floorV)) - logMin) / logRange));
89
+ }
90
+ const range = max - min;
91
+ return (v) => range <= 0 ? 0 : Math.min(1, Math.max(0, (v - min) / range));
92
+ }
93
+ function categoryOrder(seen, domain) {
94
+ const listed = (domain ?? []).map(String);
95
+ const rest = [...seen.keys()].filter(k => !listed.includes(k));
96
+ const numeric = rest.every(k => k !== '' && Number.isFinite(Number(k)));
97
+ rest.sort(numeric ? (a, b) => Number(a) - Number(b) : (a, b) => a.localeCompare(b));
98
+ return [...listed, ...rest];
99
+ }
100
+ // The categorical arm `color` and `glyph` share: the walk records which
101
+ // distinct value each admitted instance carried, and `resolve` hands every
102
+ // value its range entry once the region's table is known. A listed domain
103
+ // is the author's order and walks the range; without one the entry derives
104
+ // from the value itself, so two regions that met different value sets still
105
+ // agree on every value they share.
106
+ function categoricalChannel(read, n) {
107
+ const categories = new Map();
108
+ const indexOf = new Int32Array(n);
109
+ return {
110
+ indexOf,
111
+ collect(f, at) {
112
+ const raw = read(f);
113
+ if (raw === undefined || raw === null || raw === '') {
114
+ indexOf[at] = -1;
115
+ return;
116
+ }
117
+ const label = String(raw);
118
+ let index = categories.get(label);
119
+ if (index === undefined) {
120
+ index = categories.size;
121
+ categories.set(label, index);
122
+ }
123
+ indexOf[at] = index;
124
+ },
125
+ resolve(domain, range) {
126
+ const ofIndex = Array.from({ length: categories.size });
127
+ const entries = [];
128
+ categoryOrder(categories, domain).forEach((label, slot) => {
129
+ const value = domain
130
+ ? range[slot % range.length]
131
+ : categoricalValueColor(label, range);
132
+ const index = categories.get(label);
133
+ if (index !== undefined) {
134
+ ofIndex[index] = value;
135
+ }
136
+ entries.push({ label, value });
137
+ });
138
+ return { ofIndex, entries };
139
+ },
140
+ };
141
+ }
142
+ function hasMissing(indexOf, count) {
143
+ for (let i = 0; i < count; i++) {
144
+ if (indexOf[i] < 0) {
145
+ return true;
146
+ }
147
+ }
148
+ return false;
149
+ }
150
+ /**
151
+ * #api
152
+ * Evaluate one encoding over a feature list into dense channel arrays for
153
+ * the lanes named, the scale table each scaled channel came from, the `y`
154
+ * extremes and — when `index` is among the lanes — a hit index.
155
+ *
156
+ * A feature whose `x`, `x2` or (declared and asked-for) `y` is not finite is
157
+ * skipped and counted in `skipped`, so every array stays index-aligned with
158
+ * the Flatbush. Pure: the RPC around it owns the adapter, the filters and the
159
+ * transferables.
160
+ */
161
+ export function encodeFeatures(features, encoding, lanes, ctx = {}) {
162
+ const { jexl, report } = ctx;
163
+ const n = features.length;
164
+ const has = (lane) => lanes.includes(lane);
165
+ const readX = fieldReader(encoding.x ?? 'start', jexl);
166
+ const readX2 = fieldReader(encoding.x2 ?? 'end', jexl);
167
+ const { y: yEncoding } = encoding;
168
+ const readY = has('y') && yEncoding !== undefined
169
+ ? fieldReader(typeof yEncoding === 'object' ? valueField(yEncoding) : yEncoding, jexl)
170
+ : undefined;
171
+ const readRow = has('row') && encoding.row !== undefined
172
+ ? fieldReader(encoding.row, jexl)
173
+ : undefined;
174
+ const x = new Uint32Array(n);
175
+ const x2 = new Uint32Array(n);
176
+ const y = has('y') ? new Float32Array(n) : undefined;
177
+ const row = has('row') ? new Uint32Array(n) : undefined;
178
+ const glyph = has('glyph') ? new Uint8Array(n) : undefined;
179
+ const featureIndex = new Uint32Array(n);
180
+ let yMin = Infinity;
181
+ let yMax = -Infinity;
182
+ let count = 0;
183
+ const colorEncoding = encoding.color ?? DEFAULT_MARK_COLOR;
184
+ const declaredScale = typeof colorEncoding === 'object' ? colorEncoding : undefined;
185
+ const rampEncoding = declaredScale && declaredScale.scale !== 'categorical'
186
+ ? declaredScale
187
+ : undefined;
188
+ // Which side of the wire a ramp resolves on is the caller's lane choice: a
189
+ // shape that reads the ramp itself names `colorValue` and gets the raw
190
+ // values, and the display unions the regions' extremes into one domain.
191
+ // Anything else names `color` and the walk resolves per region.
192
+ const colorValue = rampEncoding && has('colorValue') ? new Float32Array(n) : undefined;
193
+ const color = has('color') && !colorValue ? new Uint32Array(n) : undefined;
194
+ const wantColor = color !== undefined || colorValue !== undefined;
195
+ const scaled = wantColor ? declaredScale : undefined;
196
+ const readColor = !wantColor
197
+ ? undefined
198
+ : typeof colorEncoding === 'function'
199
+ ? colorEncoding
200
+ : scaled === undefined
201
+ ? colorEvaluator(colorEncoding, jexl)
202
+ : fieldReader(scaled.field, jexl);
203
+ // A scaled channel resolves after the walk, once the table is known: the
204
+ // category per admitted instance, or a ramp's raw value, kept here.
205
+ const colorCategories = scaled?.scale === 'categorical' && readColor
206
+ ? categoricalChannel(readColor, n)
207
+ : undefined;
208
+ const rampValues = scaled && scaled.scale !== 'categorical'
209
+ ? (colorValue ?? new Float32Array(n))
210
+ : undefined;
211
+ const { glyph: glyphEncoding } = encoding;
212
+ const glyphScaled = glyph && typeof glyphEncoding === 'object' ? glyphEncoding : undefined;
213
+ const glyphCategories = glyphScaled
214
+ ? categoricalChannel(fieldReader(glyphScaled.field, jexl), n)
215
+ : undefined;
216
+ const readGlyph = glyph
217
+ ? glyphReader(typeof glyphEncoding === 'object' ? undefined : glyphEncoding, jexl)
218
+ : undefined;
219
+ for (let i = 0; i < n; i++) {
220
+ report?.(i);
221
+ const f = features[i];
222
+ const xv = Number(readX(f));
223
+ const x2v = Number(readX2(f));
224
+ const yv = readY ? Number(readY(f)) : 0;
225
+ if (!Number.isFinite(xv) || !Number.isFinite(x2v) || !Number.isFinite(yv)) {
226
+ continue;
227
+ }
228
+ x[count] = xv;
229
+ x2[count] = x2v;
230
+ if (y) {
231
+ y[count] = yv;
232
+ }
233
+ if (readY) {
234
+ if (yv < yMin) {
235
+ yMin = yv;
236
+ }
237
+ if (yv > yMax) {
238
+ yMax = yv;
239
+ }
240
+ }
241
+ if (row && readRow) {
242
+ const rv = Number(readRow(f));
243
+ row[count] = rv > 0 ? rv : 0;
244
+ }
245
+ if (glyphCategories) {
246
+ glyphCategories.collect(f, count);
247
+ }
248
+ else if (glyph && readGlyph) {
249
+ glyph[count] = readGlyph(f);
250
+ }
251
+ featureIndex[count] = i;
252
+ if (colorCategories) {
253
+ colorCategories.collect(f, count);
254
+ }
255
+ else if (rampValues && readColor) {
256
+ rampValues[count] = Number(readColor(f));
257
+ }
258
+ else if (color && readColor) {
259
+ color[count] = readColor(f);
260
+ }
261
+ count++;
262
+ }
263
+ let scale;
264
+ if (scaled?.scale === 'categorical' && colorCategories && color) {
265
+ const palette = (scaled.palette ?? categoricalPalette).map(cssColorToABGR);
266
+ const { ofIndex, entries } = colorCategories.resolve(scaled.domain, palette);
267
+ const { indexOf } = colorCategories;
268
+ for (let i = 0; i < count; i++) {
269
+ const index = indexOf[i];
270
+ color[i] = index < 0 ? FALLBACK_COLOR : ofIndex[index];
271
+ }
272
+ scale = {
273
+ kind: 'categorical',
274
+ field: scaled.field,
275
+ entries: [
276
+ ...entries.map(e => ({ label: e.label, color: e.value })),
277
+ ...(hasMissing(indexOf, count)
278
+ ? [{ label: NO_VALUE_LABEL, color: FALLBACK_COLOR }]
279
+ : []),
280
+ ],
281
+ };
282
+ }
283
+ else if (scaled && scaled.scale !== 'categorical' && rampValues) {
284
+ const extent = finiteExtremes(rampValues, count);
285
+ const domain = scaled.domain ?? extent;
286
+ const lut = buildColorRampLut(rampStops(scaled.ramp));
287
+ if (color) {
288
+ const norm = normalizer(scaled.scale, domain);
289
+ for (let i = 0; i < count; i++) {
290
+ const v = rampValues[i];
291
+ color[i] = Number.isFinite(v)
292
+ ? lutColorAt(lut, norm(v))
293
+ : FALLBACK_COLOR;
294
+ }
295
+ }
296
+ scale = {
297
+ kind: 'ramp',
298
+ field: scaled.field,
299
+ scale: scaled.scale,
300
+ domain,
301
+ pinned: scaled.domain !== undefined,
302
+ extent,
303
+ lut,
304
+ };
305
+ }
306
+ let glyphScale;
307
+ if (glyphScaled && glyphCategories && glyph) {
308
+ const { ofIndex, entries } = glyphCategories.resolve(glyphScaled.domain, glyphScaled.range ?? GLYPH_NAMES);
309
+ const codeOfIndex = Uint8Array.from(ofIndex, name => GLYPH_CODES[name]);
310
+ const { indexOf } = glyphCategories;
311
+ for (let i = 0; i < count; i++) {
312
+ const index = indexOf[i];
313
+ glyph[i] = index < 0 ? GLYPH_DISC : codeOfIndex[index];
314
+ }
315
+ glyphScale = {
316
+ kind: 'glyph',
317
+ field: glyphScaled.field,
318
+ entries: [
319
+ ...entries.map(e => ({ label: e.label, glyph: e.value })),
320
+ ...(hasMissing(indexOf, count)
321
+ ? [{ label: NO_VALUE_LABEL, glyph: 'disc' }]
322
+ : []),
323
+ ],
324
+ };
325
+ }
326
+ let flatbushData;
327
+ if (has('index') && count > 0) {
328
+ const fb = new Flatbush(count, undefined, Float64Array);
329
+ for (let i = 0; i < count; i++) {
330
+ const v = y ? y[i] : 0;
331
+ fb.add(x[i], v, x2[i], v);
332
+ }
333
+ fb.finish();
334
+ flatbushData = fb.data;
335
+ }
336
+ const encoded = {
337
+ count,
338
+ skipped: n - count,
339
+ x: x.subarray(0, count),
340
+ x2: x2.subarray(0, count),
341
+ featureIndex: featureIndex.subarray(0, count),
342
+ yMin,
343
+ yMax,
344
+ };
345
+ if (y) {
346
+ encoded.y = y.subarray(0, count);
347
+ }
348
+ if (row) {
349
+ encoded.row = row.subarray(0, count);
350
+ }
351
+ if (color) {
352
+ encoded.color = color.subarray(0, count);
353
+ }
354
+ if (colorValue) {
355
+ encoded.colorValue = colorValue.subarray(0, count);
356
+ }
357
+ if (glyph) {
358
+ encoded.glyph = glyph.subarray(0, count);
359
+ }
360
+ if (flatbushData) {
361
+ encoded.flatbushData = flatbushData;
362
+ }
363
+ if (scale) {
364
+ encoded.scale = scale;
365
+ }
366
+ if (glyphScale) {
367
+ encoded.glyphScale = glyphScale;
368
+ }
369
+ return encoded;
370
+ }
371
+ function finiteExtremes(values, count) {
372
+ let min = Infinity;
373
+ let max = -Infinity;
374
+ for (let i = 0; i < count; i++) {
375
+ const v = values[i];
376
+ if (Number.isFinite(v)) {
377
+ if (v < min) {
378
+ min = v;
379
+ }
380
+ if (v > max) {
381
+ max = v;
382
+ }
383
+ }
384
+ }
385
+ return min === Infinity ? [0, 1] : [min, max];
386
+ }
387
+ /**
388
+ * #api
389
+ * A CSS colour or `jexl:` colour expression as a per-feature packed ABGR —
390
+ * the unscaled arm of {@link ColorEncoding}, on its own for a display that
391
+ * carries a plain `color` slot.
392
+ */
393
+ export function colorEvaluator(color, jexl) {
394
+ if (isJexl(color)) {
395
+ const expr = jexlExpression(color, jexl);
396
+ // A jexl colour answers from a handful of strings over a million
397
+ // features; parsing each answer once is a third of the arm's cost
398
+ // (packages/core/benches/encodeFeatures.bench.ts).
399
+ const packed = new Map();
400
+ return feature => {
401
+ const v = expr.eval(buildJexlContext({ feature }));
402
+ if (typeof v !== 'string') {
403
+ return FALLBACK_COLOR;
404
+ }
405
+ let c = packed.get(v);
406
+ if (c === undefined) {
407
+ c = cssColorToABGR(v);
408
+ packed.set(v, c);
409
+ }
410
+ return c;
411
+ };
412
+ }
413
+ const constant = cssColorToABGR(color);
414
+ return () => constant;
415
+ }
416
+ /**
417
+ * #api
418
+ * The buffers an {@link EncodedChannels} owns, for `rpcResult`'s transfer
419
+ * list.
420
+ */
421
+ export function encodedChannelTransferables(c) {
422
+ const buffers = [
423
+ c.x.buffer,
424
+ c.x2.buffer,
425
+ c.featureIndex.buffer,
426
+ ];
427
+ for (const lane of [c.y, c.row, c.color, c.colorValue, c.glyph]) {
428
+ if (lane) {
429
+ buffers.push(lane.buffer);
430
+ }
431
+ }
432
+ if (c.flatbushData) {
433
+ buffers.push(c.flatbushData);
434
+ }
435
+ return buffers;
436
+ }