@santi020k/lumen-core 3.0.1 → 4.1.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/PHONE_FLAG_LICENSE.txt +21 -0
- package/README.md +384 -3
- package/THIRD_PARTY_NOTICES.md +16 -2
- package/dist/ai-surfaces.d.ts +25 -0
- package/dist/ai-surfaces.d.ts.map +1 -0
- package/dist/ai-surfaces.js +162 -0
- package/dist/amount-field.d.ts +22 -0
- package/dist/amount-field.d.ts.map +1 -0
- package/dist/amount-field.js +207 -0
- package/dist/attachments.d.ts +15 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +123 -0
- package/dist/bullet-chart.d.ts +33 -0
- package/dist/bullet-chart.d.ts.map +1 -0
- package/dist/bullet-chart.js +80 -0
- package/dist/chart-activation.d.ts +32 -0
- package/dist/chart-activation.d.ts.map +1 -0
- package/dist/chart-activation.js +97 -0
- package/dist/chart-interaction.d.ts +12 -0
- package/dist/chart-interaction.d.ts.map +1 -0
- package/dist/chart-interaction.js +274 -0
- package/dist/chart-models.d.ts +156 -0
- package/dist/chart-models.d.ts.map +1 -0
- package/dist/chart-models.js +348 -0
- package/dist/chart-motion.d.ts +6 -0
- package/dist/chart-motion.d.ts.map +1 -0
- package/dist/chart-motion.js +107 -0
- package/dist/charts.d.ts +58 -3
- package/dist/charts.d.ts.map +1 -1
- package/dist/charts.js +247 -18
- package/dist/code.js +1 -1
- package/dist/combobox.d.ts +7 -0
- package/dist/combobox.d.ts.map +1 -0
- package/dist/combobox.js +253 -0
- package/dist/comparison-chart.d.ts +32 -0
- package/dist/comparison-chart.d.ts.map +1 -0
- package/dist/comparison-chart.js +64 -0
- package/dist/components.d.ts +210 -25
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +102 -26
- package/dist/dashboard.d.ts +16 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +21 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +21 -5
- package/dist/dates.d.ts +13 -0
- package/dist/dates.d.ts.map +1 -0
- package/dist/dates.js +37 -0
- package/dist/device-frame.d.ts +25 -0
- package/dist/device-frame.d.ts.map +1 -0
- package/dist/device-frame.js +50 -0
- package/dist/direction.d.ts +3 -0
- package/dist/direction.d.ts.map +1 -0
- package/dist/direction.js +10 -0
- package/dist/extended-charts.d.ts +95 -0
- package/dist/extended-charts.d.ts.map +1 -0
- package/dist/extended-charts.js +175 -0
- package/dist/figma.d.ts.map +1 -1
- package/dist/figma.js +10 -1
- package/dist/floating-panel.d.ts +24 -0
- package/dist/floating-panel.d.ts.map +1 -0
- package/dist/floating-panel.js +203 -0
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +18 -4
- package/dist/icon-data.generated.d.ts +1 -1
- package/dist/icon-data.generated.d.ts.map +1 -1
- package/dist/icon-data.generated.js +1 -1
- package/dist/icons.js +3 -4
- package/dist/illustrations.generated.d.ts +1 -1
- package/dist/image-comparison.d.ts +14 -0
- package/dist/image-comparison.d.ts.map +1 -0
- package/dist/image-comparison.js +48 -0
- package/dist/index.d.ts +37 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -3
- package/dist/loading.d.ts +34 -0
- package/dist/loading.d.ts.map +1 -0
- package/dist/loading.js +70 -0
- package/dist/media-viewport.d.ts +24 -0
- package/dist/media-viewport.d.ts.map +1 -0
- package/dist/media-viewport.js +119 -0
- package/dist/media-workspace.d.ts +27 -0
- package/dist/media-workspace.d.ts.map +1 -0
- package/dist/media-workspace.js +83 -0
- package/dist/message-scroller.d.ts +14 -0
- package/dist/message-scroller.d.ts.map +1 -0
- package/dist/message-scroller.js +113 -0
- package/dist/motion-workflows.d.ts +24 -0
- package/dist/motion-workflows.d.ts.map +1 -0
- package/dist/motion-workflows.js +220 -0
- package/dist/motion.d.ts +12 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +96 -0
- package/dist/native-input.d.ts +25 -0
- package/dist/native-input.d.ts.map +1 -0
- package/dist/native-input.js +188 -0
- package/dist/phone-flags.generated.d.ts +2 -0
- package/dist/phone-flags.generated.d.ts.map +1 -0
- package/dist/phone-flags.generated.js +263 -0
- package/dist/phone.d.ts +4 -0
- package/dist/phone.d.ts.map +1 -1
- package/dist/phone.js +6 -1
- package/dist/rich-text.d.ts +7 -0
- package/dist/rich-text.d.ts.map +1 -1
- package/dist/rich-text.js +13 -0
- package/dist/rive.d.ts +37 -0
- package/dist/rive.d.ts.map +1 -0
- package/dist/rive.js +129 -0
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +63 -23
- package/dist/tab-indicator.d.ts +3 -0
- package/dist/tab-indicator.d.ts.map +1 -0
- package/dist/tab-indicator.js +57 -0
- package/dist/theme-audit.d.ts +16 -0
- package/dist/theme-audit.d.ts.map +1 -0
- package/dist/theme-audit.js +57 -0
- package/dist/theme-builder.d.ts +7 -1
- package/dist/theme-builder.d.ts.map +1 -1
- package/dist/theme-builder.js +45 -23
- package/dist/theme-presets.generated.d.ts +217 -0
- package/dist/theme-presets.generated.d.ts.map +1 -0
- package/dist/theme-presets.generated.js +231 -0
- package/dist/theme.d.ts +11 -3
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +56 -6
- package/dist/virtual-collection.d.ts +14 -0
- package/dist/virtual-collection.d.ts.map +1 -0
- package/dist/virtual-collection.js +132 -0
- package/dist/virtual-list.d.ts +7 -0
- package/dist/virtual-list.d.ts.map +1 -0
- package/dist/virtual-list.js +135 -0
- package/dist/virtual-window.d.ts +19 -0
- package/dist/virtual-window.d.ts.map +1 -0
- package/dist/virtual-window.js +84 -0
- package/dist/visual-effects.d.ts +6 -0
- package/dist/visual-effects.d.ts.map +1 -0
- package/dist/visual-effects.js +44 -0
- package/dist/world-map-data.generated.d.ts +9 -0
- package/dist/world-map-data.generated.d.ts.map +1 -0
- package/dist/world-map-data.generated.js +240 -0
- package/dist/world-map-navigation.d.ts +16 -0
- package/dist/world-map-navigation.d.ts.map +1 -0
- package/dist/world-map-navigation.js +53 -0
- package/dist/world-map-pan.d.ts +3 -0
- package/dist/world-map-pan.d.ts.map +1 -0
- package/dist/world-map-pan.js +66 -0
- package/dist/world-map-zoom.d.ts +13 -0
- package/dist/world-map-zoom.d.ts.map +1 -0
- package/dist/world-map-zoom.js +61 -0
- package/dist/world-map.d.ts +71 -0
- package/dist/world-map.d.ts.map +1 -0
- package/dist/world-map.js +143 -0
- package/package.json +51 -4
package/dist/charts.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { createLumenChartActivationController, createLumenChartDatumActivation, createLumenHeatmapDatumActivation, createLumenRangeDatumActivation, parseLumenChartDatumActivation } from './chart-activation.js';
|
|
1
2
|
export const lumenChartTones = [
|
|
2
3
|
'series-1',
|
|
3
4
|
'series-2',
|
|
@@ -25,11 +26,18 @@ const formatDefaultLumenChartSummary = (summary, formatValue) => {
|
|
|
25
26
|
`Values range from ${formatValue(summary.minimum ?? 0)} to ${formatValue(summary.maximum ?? 0)}.${missing}`;
|
|
26
27
|
};
|
|
27
28
|
export const lumenChartLabels = Object.freeze({
|
|
29
|
+
count: 'Count',
|
|
30
|
+
density: 'Frequency density',
|
|
31
|
+
end: 'End',
|
|
32
|
+
invalidData: 'Chart data is invalid. Check the supplied values.',
|
|
33
|
+
start: 'Start',
|
|
28
34
|
category: 'Category',
|
|
29
35
|
chartData: 'Chart data',
|
|
30
36
|
chartLegend: 'Chart legend',
|
|
31
37
|
column: 'Column',
|
|
32
38
|
empty: 'No chart data available.',
|
|
39
|
+
exploreData: 'Explore chart data',
|
|
40
|
+
formatDatumAction: (context) => `Open details: ${context}`,
|
|
33
41
|
formatHeatmapSummary: (count) => count === 0 ?
|
|
34
42
|
'No chart data available.' :
|
|
35
43
|
`${count} available heatmap ${count === 1 ? 'cell' : 'cells'}.`,
|
|
@@ -88,9 +96,9 @@ export const getLumenChartDomain = (values, includeZero = true) => {
|
|
|
88
96
|
max = Math.max(0, max);
|
|
89
97
|
}
|
|
90
98
|
if (min === max) {
|
|
91
|
-
const offset = Math.abs(min || 1) * 0.1;
|
|
92
|
-
min
|
|
93
|
-
max
|
|
99
|
+
const offset = Math.max(Number.MIN_VALUE, Math.abs(min || 1) * 0.1);
|
|
100
|
+
min = Math.max(-Number.MAX_VALUE, min - offset);
|
|
101
|
+
max = Math.min(Number.MAX_VALUE, max + offset);
|
|
94
102
|
}
|
|
95
103
|
return { max, min };
|
|
96
104
|
};
|
|
@@ -103,9 +111,12 @@ const resolveLumenChartDomain = (values, requested, includeZero) => {
|
|
|
103
111
|
};
|
|
104
112
|
export const scaleLumenChartValue = (value, domain, rangeStart, rangeEnd) => {
|
|
105
113
|
const domainSize = domain.max - domain.min;
|
|
106
|
-
if (!Number.isFinite(value) || domainSize === 0)
|
|
114
|
+
if (!Number.isFinite(value) || !Number.isFinite(domain.min) || !Number.isFinite(domain.max) || domainSize === 0)
|
|
107
115
|
return rangeStart;
|
|
108
|
-
|
|
116
|
+
// A valid domain can span opposite finite extremes whose difference overflows.
|
|
117
|
+
const ratio = Number.isFinite(domainSize) ?
|
|
118
|
+
(value - domain.min) / domainSize :
|
|
119
|
+
(value / 2 - domain.min / 2) / (domain.max / 2 - domain.min / 2);
|
|
109
120
|
return rangeStart + ratio * (rangeEnd - rangeStart);
|
|
110
121
|
};
|
|
111
122
|
export const getLumenChartTicks = (domain, count = 5) => {
|
|
@@ -199,6 +210,83 @@ export const getLumenChartAxisPadding = (labels, minimum = 44) => {
|
|
|
199
210
|
const estimated = widestLabelWidth + chartAxisLabelGap;
|
|
200
211
|
return Math.min(maximumChartAxisPadding, Math.max(safeMinimum, estimated));
|
|
201
212
|
};
|
|
213
|
+
const fitLumenChartLabel = (label, maximumWidth) => {
|
|
214
|
+
if (estimateLumenChartAxisLabelWidth(label) <= maximumWidth)
|
|
215
|
+
return label;
|
|
216
|
+
const suffix = '…';
|
|
217
|
+
let result = '';
|
|
218
|
+
let width = estimateLumenChartAxisLabelWidth(suffix);
|
|
219
|
+
for (const character of label) {
|
|
220
|
+
width += Math.max(chartAxisLabelCharacterWidth, getLumenChartAxisCharacterWidth(character));
|
|
221
|
+
if (width > maximumWidth)
|
|
222
|
+
break;
|
|
223
|
+
result += character;
|
|
224
|
+
}
|
|
225
|
+
return result + suffix;
|
|
226
|
+
};
|
|
227
|
+
const getLumenChartTickAnchor = (index, count) => {
|
|
228
|
+
if (count === 1)
|
|
229
|
+
return 'middle';
|
|
230
|
+
if (index === 0)
|
|
231
|
+
return 'start';
|
|
232
|
+
return index === count - 1 ? 'end' : 'middle';
|
|
233
|
+
};
|
|
234
|
+
const selectLumenChartCategoryTicks = (candidates, gap) => {
|
|
235
|
+
const first = candidates[0];
|
|
236
|
+
const last = candidates.at(-1);
|
|
237
|
+
if (!first || !last)
|
|
238
|
+
return [];
|
|
239
|
+
if (candidates.length === 1)
|
|
240
|
+
return [first];
|
|
241
|
+
const lastStart = last.position - estimateLumenChartAxisLabelWidth(last.label);
|
|
242
|
+
const ticks = [first];
|
|
243
|
+
let previousEnd = first.position + estimateLumenChartAxisLabelWidth(first.label);
|
|
244
|
+
for (const candidate of candidates.slice(1, -1)) {
|
|
245
|
+
const halfWidth = estimateLumenChartAxisLabelWidth(candidate.label) / 2;
|
|
246
|
+
const labelStart = candidate.position - halfWidth;
|
|
247
|
+
const labelEnd = candidate.position + halfWidth;
|
|
248
|
+
if (labelStart < previousEnd + gap || labelEnd + gap > lastStart)
|
|
249
|
+
continue;
|
|
250
|
+
ticks.push(candidate);
|
|
251
|
+
previousEnd = labelEnd;
|
|
252
|
+
}
|
|
253
|
+
if (lastStart >= previousEnd + gap)
|
|
254
|
+
ticks.push(last);
|
|
255
|
+
return ticks;
|
|
256
|
+
};
|
|
257
|
+
const resolveLumenChartTickPositions = (count, end, positions, start) => {
|
|
258
|
+
const validPositions = positions?.length === count && positions.every((position, index) => (Number.isFinite(position) && position >= start && position <= end && position >= (positions[index - 1] ?? start)));
|
|
259
|
+
if (validPositions)
|
|
260
|
+
return positions;
|
|
261
|
+
return Array.from({ length: count }, (_, index) => (start + (count === 1 ? 0.5 : index / (count - 1)) * (end - start)));
|
|
262
|
+
};
|
|
263
|
+
/** Selects readable axis labels without changing or downsampling the underlying chart data. */
|
|
264
|
+
export const getLumenChartCategoryTicks = (labels, { end, minimumGap = 16, positions, start }) => {
|
|
265
|
+
if (labels.length === 0 || !Number.isFinite(start) || !Number.isFinite(end) || end < start)
|
|
266
|
+
return [];
|
|
267
|
+
const gap = Number.isFinite(minimumGap) ? Math.max(0, minimumGap) : 16;
|
|
268
|
+
const resolvedPositions = resolveLumenChartTickPositions(labels.length, end, positions, start);
|
|
269
|
+
const span = (resolvedPositions.at(-1) ?? end) - (resolvedPositions[0] ?? start);
|
|
270
|
+
const maximumWidth = Math.max(0, labels.length === 1 ? end - start : (span - gap) / 2);
|
|
271
|
+
const candidates = labels.map((label, index) => ({
|
|
272
|
+
index,
|
|
273
|
+
label: fitLumenChartLabel(label, maximumWidth),
|
|
274
|
+
position: resolvedPositions[index] ?? start,
|
|
275
|
+
textAnchor: getLumenChartTickAnchor(index, labels.length)
|
|
276
|
+
}));
|
|
277
|
+
return selectLumenChartCategoryTicks(candidates, gap);
|
|
278
|
+
};
|
|
279
|
+
/** Axis labels prefer xLabel; detail labels prefer an explicitly supplied full formatter. */
|
|
280
|
+
export const getLumenChartCategoryLabel = (series, category, formatCategory, context = 'axis') => {
|
|
281
|
+
if (context === 'detail' && formatCategory)
|
|
282
|
+
return formatCategory(category);
|
|
283
|
+
for (const item of series) {
|
|
284
|
+
const label = item.data.find(datum => datum.x === category)?.xLabel;
|
|
285
|
+
if (label !== undefined)
|
|
286
|
+
return label;
|
|
287
|
+
}
|
|
288
|
+
return formatCategory?.(category) ?? String(category);
|
|
289
|
+
};
|
|
202
290
|
export const resolveLumenChartTone = (tone, index = 0) => tone ?? lumenChartTones[index % 8] ?? 'series-1';
|
|
203
291
|
export const getLumenChartToneClassName = (tone, index = 0) => lumenChartToneClassNames[resolveLumenChartTone(tone, index)];
|
|
204
292
|
export const getLumenPieChartVariantClassName = (variant) => lumenPieChartVariantClassNames[variant];
|
|
@@ -302,6 +390,8 @@ export const getLumenChartNumericX = (value, scale) => {
|
|
|
302
390
|
if (typeof value === 'number')
|
|
303
391
|
return Number.isFinite(value) ? value : null;
|
|
304
392
|
if (scale === 'linear') {
|
|
393
|
+
if (!value.trim())
|
|
394
|
+
return null;
|
|
305
395
|
const numeric = Number(value);
|
|
306
396
|
return Number.isFinite(numeric) ? numeric : null;
|
|
307
397
|
}
|
|
@@ -454,7 +544,7 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
454
544
|
const layout = options.layout ?? 'grouped';
|
|
455
545
|
const categories = getLumenChartCategories(series);
|
|
456
546
|
const indexedSeries = series.map(indexLumenChartSeries);
|
|
457
|
-
const values =
|
|
547
|
+
const values = indexedSeries.flatMap(item => [...item.values()].map(datum => datum.y));
|
|
458
548
|
const calculatedDomain = layout === 'stacked' ?
|
|
459
549
|
getStackedDomain(series, categories) :
|
|
460
550
|
getLumenChartDomain(values);
|
|
@@ -465,11 +555,16 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
465
555
|
const margin = orientation === 'horizontal' ?
|
|
466
556
|
{
|
|
467
557
|
bottom: 24,
|
|
468
|
-
left: Math.max(64, Math.min(240, options.categoryWidth ?? 112)),
|
|
558
|
+
left: Math.max(64, Math.min(240, options.categoryWidth ?? getLumenChartAxisPadding(categories.map(category => getLumenChartCategoryLabel(series, category, options.formatCategory)), 112))),
|
|
469
559
|
right: 20,
|
|
470
560
|
top: 16
|
|
471
561
|
} :
|
|
472
|
-
{
|
|
562
|
+
{
|
|
563
|
+
bottom: 52,
|
|
564
|
+
left: getLumenChartAxisPadding(getLumenChartTicks(domain).map(tick => (options.formatValue ?? String)(tick)), 52),
|
|
565
|
+
right: 16,
|
|
566
|
+
top: 16
|
|
567
|
+
};
|
|
473
568
|
const plotWidth = Math.max(1, width - margin.left - margin.right);
|
|
474
569
|
const plotHeight = Math.max(1, height - margin.top - margin.bottom);
|
|
475
570
|
const categorySize = (orientation === 'horizontal' ? plotHeight : plotWidth) /
|
|
@@ -487,9 +582,7 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
487
582
|
let negativeOffset = 0;
|
|
488
583
|
categoryPositions.push({
|
|
489
584
|
category,
|
|
490
|
-
label: series
|
|
491
|
-
.flatMap(item => item.data)
|
|
492
|
-
.find(datum => datum.x === category)?.xLabel ?? category,
|
|
585
|
+
label: getLumenChartCategoryLabel(series, category, options.formatCategory),
|
|
493
586
|
x: orientation === 'horizontal' ?
|
|
494
587
|
margin.left - 8 :
|
|
495
588
|
categoryStart + usableCategorySize / 2,
|
|
@@ -557,6 +650,7 @@ export const createLumenBarGeometry = (series, options = {}) => {
|
|
|
557
650
|
categories: categoryPositions,
|
|
558
651
|
domain,
|
|
559
652
|
height,
|
|
653
|
+
margin,
|
|
560
654
|
marks,
|
|
561
655
|
width
|
|
562
656
|
};
|
|
@@ -622,8 +716,18 @@ const validateLumenChartDatumNumbers = (datum, context) => {
|
|
|
622
716
|
}
|
|
623
717
|
};
|
|
624
718
|
const validateLumenChartDatumX = (datum, context) => {
|
|
625
|
-
if (context.xScale === 'categorical')
|
|
719
|
+
if (context.xScale === 'categorical') {
|
|
720
|
+
const key = lumenChartCategoryKey(datum.x);
|
|
721
|
+
if (context.categories.has(key)) {
|
|
722
|
+
context.issues.push({
|
|
723
|
+
code: 'duplicate-category',
|
|
724
|
+
message: 'Category x values must be unique within a series. Use xLabel for repeated display labels.',
|
|
725
|
+
path: `${context.path}.x`
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
context.categories.add(key);
|
|
626
729
|
return context.previousX;
|
|
730
|
+
}
|
|
627
731
|
const numericX = getLumenChartNumericX(datum.x, context.xScale);
|
|
628
732
|
if (numericX === null) {
|
|
629
733
|
context.issues.push({
|
|
@@ -661,9 +765,11 @@ export const validateLumenChartSeries = (series, xScale = 'categorical') => {
|
|
|
661
765
|
}
|
|
662
766
|
seriesIds.add(item.id);
|
|
663
767
|
const datumIds = new Set();
|
|
768
|
+
const categories = new Set();
|
|
664
769
|
let previousX = Number.NEGATIVE_INFINITY;
|
|
665
770
|
for (const [datumIndex, datum] of item.data.entries()) {
|
|
666
771
|
previousX = validateLumenChartDatum(datum, {
|
|
772
|
+
categories,
|
|
667
773
|
datumIds,
|
|
668
774
|
issues,
|
|
669
775
|
path: `${seriesPath}.data[${datumIndex}]`,
|
|
@@ -723,6 +829,68 @@ export const appendLumenChartDatum = (series, datum, maximumPoints = 500) => {
|
|
|
723
829
|
data: data.slice(Math.max(0, data.length - safeMaximum))
|
|
724
830
|
};
|
|
725
831
|
};
|
|
832
|
+
const getLumenScatterNumericX = (value, scale) => {
|
|
833
|
+
const numeric = getLumenChartNumericX(value, scale === 'log' ? 'linear' : scale);
|
|
834
|
+
return scale === 'log' && numeric !== null && numeric <= 0 ? null : numeric;
|
|
835
|
+
};
|
|
836
|
+
const validScatterDomain = (domain, scale) => {
|
|
837
|
+
const finite = Number.isFinite(domain.min) && Number.isFinite(domain.max);
|
|
838
|
+
return finite && domain.max > domain.min && (scale !== 'log' || domain.min > 0);
|
|
839
|
+
};
|
|
840
|
+
const getLumenScatterXDomain = (data, scale, requested) => {
|
|
841
|
+
const values = data.map(datum => getLumenScatterNumericX(datum.x, scale));
|
|
842
|
+
const calculated = scale === 'log' && !values.some(value => value !== null) ?
|
|
843
|
+
{ min: 1, max: 10 } :
|
|
844
|
+
getLumenChartDomain(values, false);
|
|
845
|
+
const domain = { min: requested?.min ?? calculated.min, max: requested?.max ?? calculated.max };
|
|
846
|
+
if (!validScatterDomain(domain, scale))
|
|
847
|
+
throw new RangeError('Scatter x domain must be finite, ordered, and positive for log scales');
|
|
848
|
+
return domain;
|
|
849
|
+
};
|
|
850
|
+
export const scaleLumenScatterX = (value, domain, rangeStart, rangeEnd, scale = 'linear') => {
|
|
851
|
+
if (scale !== 'log')
|
|
852
|
+
return scaleLumenChartValue(value, domain, rangeStart, rangeEnd);
|
|
853
|
+
if (value <= 0 || !Number.isFinite(value))
|
|
854
|
+
return Number.NaN;
|
|
855
|
+
return scaleLumenChartValue(Math.log10(value), { min: Math.log10(domain.min), max: Math.log10(domain.max) }, rangeStart, rangeEnd);
|
|
856
|
+
};
|
|
857
|
+
export const getLumenScatterXTicks = (domain, scale) => scale === 'log' ?
|
|
858
|
+
getLumenChartTicks({ min: Math.log10(domain.min), max: Math.log10(domain.max) }).map(value => 10 ** value) :
|
|
859
|
+
getLumenChartTicks(domain);
|
|
860
|
+
const scatterReferenceCoordinate = (value, fallback, project) => {
|
|
861
|
+
if (value === undefined)
|
|
862
|
+
return fallback;
|
|
863
|
+
return project(value);
|
|
864
|
+
};
|
|
865
|
+
const isLumenScatterReference = (value) => {
|
|
866
|
+
if (typeof value !== 'object' || value === null || !('id' in value) || !('label' in value))
|
|
867
|
+
return false;
|
|
868
|
+
if (typeof value.id !== 'string' || typeof value.label !== 'string')
|
|
869
|
+
return false;
|
|
870
|
+
return ['x', 'y', 'xEnd', 'yEnd'].every(key => {
|
|
871
|
+
const coordinate = Reflect.get(value, key);
|
|
872
|
+
return coordinate === undefined || (typeof coordinate === 'number' && Number.isFinite(coordinate));
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
export const createLumenScatterReferences = (references, geometry, scale = 'linear', padding = 44) => {
|
|
876
|
+
const result = [];
|
|
877
|
+
if (!Array.isArray(references))
|
|
878
|
+
return result;
|
|
879
|
+
const x = (value) => scaleLumenScatterX(value, geometry.xDomain, padding, geometry.width - padding, scale);
|
|
880
|
+
const y = (value) => scaleLumenChartValue(value, geometry.domain, geometry.height - padding, padding);
|
|
881
|
+
for (const reference of references) {
|
|
882
|
+
if (!isLumenScatterReference(reference))
|
|
883
|
+
continue;
|
|
884
|
+
const region = reference.xEnd !== undefined && reference.yEnd !== undefined;
|
|
885
|
+
const x1 = scatterReferenceCoordinate(reference.x, padding, x);
|
|
886
|
+
const y1 = scatterReferenceCoordinate(reference.y, padding, y);
|
|
887
|
+
const x2 = scatterReferenceCoordinate(reference.xEnd, reference.x === undefined ? geometry.width - padding : x1, x);
|
|
888
|
+
const y2 = scatterReferenceCoordinate(reference.yEnd, reference.y === undefined ? geometry.height - padding : y1, y);
|
|
889
|
+
if ([x1, x2, y1, y2].every(Number.isFinite))
|
|
890
|
+
result.push({ ...reference, x1, x2, y1, y2, region });
|
|
891
|
+
}
|
|
892
|
+
return result;
|
|
893
|
+
};
|
|
726
894
|
const resolveLumenScatterGeometryOptions = (options) => ({
|
|
727
895
|
height: options.height ?? 320,
|
|
728
896
|
maximumRadius: options.maximumRadius ?? 18,
|
|
@@ -734,7 +902,7 @@ const resolveLumenScatterGeometryOptions = (options) => ({
|
|
|
734
902
|
xScale: options.xScale ?? 'linear'
|
|
735
903
|
});
|
|
736
904
|
const createLumenScatterPoint = (datum, context) => {
|
|
737
|
-
const numericX =
|
|
905
|
+
const numericX = getLumenScatterNumericX(datum.x, context.xScale);
|
|
738
906
|
if (numericX === null || !isAvailableLumenChartY(datum.y))
|
|
739
907
|
return null;
|
|
740
908
|
const radius = datum.size === null || datum.size === undefined || !Number.isFinite(datum.size) || datum.size < 0 ?
|
|
@@ -746,20 +914,52 @@ const createLumenScatterPoint = (datum, context) => {
|
|
|
746
914
|
seriesId: context.series.id,
|
|
747
915
|
seriesLabel: context.series.label,
|
|
748
916
|
tone: resolveLumenChartTone(datum.tone ?? context.series.tone, context.seriesIndex),
|
|
749
|
-
xCoordinate:
|
|
917
|
+
xCoordinate: scaleLumenScatterX(numericX, context.xDomain, context.padding, context.width - context.padding, context.xScale),
|
|
750
918
|
yCoordinate: scaleLumenChartValue(datum.y, context.domain, context.height - context.padding, context.padding)
|
|
751
919
|
};
|
|
752
920
|
};
|
|
921
|
+
const finiteLumenScatterBound = (value, fallback, scale) => (Number.isFinite(value) && (scale !== 'log' || value > 0) ? value : fallback);
|
|
922
|
+
// Automatic extents leave room for the complete bubble, including its stroke.
|
|
923
|
+
// Explicit bounds remain exact so consumers can intentionally crop a viewport.
|
|
924
|
+
const padLumenScatterDomain = (domain, values, requested, extent, radius, scale = 'linear') => {
|
|
925
|
+
if (values.length === 0 || extent <= 0)
|
|
926
|
+
return domain;
|
|
927
|
+
const project = (value) => scale === 'log' ? Math.log10(value) : value;
|
|
928
|
+
const fromScale = (value) => scale === 'log' ? 10 ** value : value;
|
|
929
|
+
const min = project(domain.min);
|
|
930
|
+
const max = project(domain.max);
|
|
931
|
+
const inset = Math.min(radius + 2, extent / 4);
|
|
932
|
+
const extra = (max - min) * inset / (extent - 2 * inset);
|
|
933
|
+
const projected = values.map(project);
|
|
934
|
+
const bounds = projected.reduce((result, value) => ({
|
|
935
|
+
min: Math.min(result.min, value), max: Math.max(result.max, value)
|
|
936
|
+
}), { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY });
|
|
937
|
+
const paddedMin = fromScale(Math.min(min, bounds.min - extra));
|
|
938
|
+
const paddedMax = fromScale(Math.max(max, bounds.max + extra));
|
|
939
|
+
return {
|
|
940
|
+
min: requested?.min ?? finiteLumenScatterBound(paddedMin, domain.min, scale),
|
|
941
|
+
max: requested?.max ?? finiteLumenScatterBound(paddedMax, domain.max, scale)
|
|
942
|
+
};
|
|
943
|
+
};
|
|
753
944
|
export const createLumenScatterGeometry = (series, options = {}) => {
|
|
754
945
|
const { height, maximumRadius: requestedMaximumRadius, minimumRadius: requestedMinimumRadius, padding, requestedDomain, requestedXDomain, width, xScale } = resolveLumenScatterGeometryOptions(options);
|
|
755
946
|
const data = series.flatMap(item => item.data);
|
|
756
|
-
const projectedData = data.filter(datum => (
|
|
757
|
-
const
|
|
758
|
-
|
|
947
|
+
const projectedData = data.filter(datum => (getLumenScatterNumericX(datum.x, xScale) !== null && isAvailableLumenChartY(datum.y)));
|
|
948
|
+
const calculatedDomain = resolveLumenChartDomain(projectedData.map(datum => datum.y), requestedDomain, false);
|
|
949
|
+
if (!validScatterDomain(calculatedDomain, 'linear')) {
|
|
950
|
+
throw new RangeError('Scatter y domain must be finite and increasing');
|
|
951
|
+
}
|
|
952
|
+
const calculatedXDomain = getLumenScatterXDomain(projectedData, xScale, requestedXDomain);
|
|
759
953
|
const sizes = projectedData.map(datum => (datum.size !== undefined && datum.size !== null && datum.size >= 0 ? datum.size : null));
|
|
760
954
|
const sizeDomain = getLumenChartDomain(sizes, false);
|
|
761
955
|
const minimumRadius = Math.max(1, requestedMinimumRadius);
|
|
762
956
|
const maximumRadius = Math.max(minimumRadius, requestedMaximumRadius);
|
|
957
|
+
const yValues = projectedData.flatMap(datum => datum.y === null ? [] : [datum.y]);
|
|
958
|
+
const domain = padLumenScatterDomain(calculatedDomain, yValues, requestedDomain, height - padding * 2, maximumRadius);
|
|
959
|
+
const xDomain = padLumenScatterDomain(calculatedXDomain, projectedData.flatMap(datum => {
|
|
960
|
+
const value = getLumenScatterNumericX(datum.x, xScale);
|
|
961
|
+
return value === null ? [] : [value];
|
|
962
|
+
}), requestedXDomain, width - padding * 2, maximumRadius, xScale);
|
|
763
963
|
const points = [];
|
|
764
964
|
for (const [seriesIndex, item] of series.entries()) {
|
|
765
965
|
for (const datum of item.data) {
|
|
@@ -788,6 +988,33 @@ const uniqueLumenChartCategories = (values) => {
|
|
|
788
988
|
categories.set(lumenChartCategoryKey(value), value);
|
|
789
989
|
return [...categories.values()];
|
|
790
990
|
};
|
|
991
|
+
const isHeatmapRecord = (value) => {
|
|
992
|
+
if (Array.isArray(value))
|
|
993
|
+
return false;
|
|
994
|
+
return typeof value === 'object' && value !== null;
|
|
995
|
+
};
|
|
996
|
+
const isHeatmapCoordinate = (value) => typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));
|
|
997
|
+
const isOptionalHeatmapLabel = (value) => value === undefined || typeof value === 'string';
|
|
998
|
+
const isHeatmapTone = (value) => {
|
|
999
|
+
if (value === undefined)
|
|
1000
|
+
return true;
|
|
1001
|
+
return lumenChartTones.some(tone => tone === value);
|
|
1002
|
+
};
|
|
1003
|
+
const isHeatmapDatum = (datum) => {
|
|
1004
|
+
if (!isHeatmapRecord(datum))
|
|
1005
|
+
return false;
|
|
1006
|
+
return isHeatmapCoordinate(datum.x) && isHeatmapCoordinate(datum.y) &&
|
|
1007
|
+
// Non-finite measurements are missing cells; coordinates still require finite numbers.
|
|
1008
|
+
(datum.value === null || typeof datum.value === 'number') &&
|
|
1009
|
+
[datum.id, datum.label, datum.xLabel, datum.yLabel].every(isOptionalHeatmapLabel) && isHeatmapTone(datum.tone);
|
|
1010
|
+
};
|
|
1011
|
+
/** Reject the whole decoded collection before adapters read or format cell fields. */
|
|
1012
|
+
export const normalizeLumenHeatmapData = (data) => {
|
|
1013
|
+
if (!Array.isArray(data))
|
|
1014
|
+
return [];
|
|
1015
|
+
const rows = Array.from(data);
|
|
1016
|
+
return rows.every(isHeatmapDatum) ? rows : [];
|
|
1017
|
+
};
|
|
791
1018
|
const createLumenHeatmapCell = (datum, context) => {
|
|
792
1019
|
const xIndex = context.xIndexes.get(lumenChartCategoryKey(datum.x));
|
|
793
1020
|
const yIndex = context.yIndexes.get(lumenChartCategoryKey(datum.y));
|
|
@@ -804,7 +1031,8 @@ const createLumenHeatmapCell = (datum, context) => {
|
|
|
804
1031
|
yCoordinate: yIndex * context.cellHeight
|
|
805
1032
|
};
|
|
806
1033
|
};
|
|
807
|
-
export const createLumenHeatmapGeometry = (
|
|
1034
|
+
export const createLumenHeatmapGeometry = (rawData, width = 640, height = 320) => {
|
|
1035
|
+
const data = normalizeLumenHeatmapData(rawData);
|
|
808
1036
|
const xCategories = uniqueLumenChartCategories(data.map(datum => datum.x));
|
|
809
1037
|
const yCategories = uniqueLumenChartCategories(data.map(datum => datum.y));
|
|
810
1038
|
const domain = getLumenChartDomain(data.map(datum => datum.value), false);
|
|
@@ -873,3 +1101,4 @@ export const createLumenRangeGeometry = (data, options = {}) => {
|
|
|
873
1101
|
points: segments.flat()
|
|
874
1102
|
};
|
|
875
1103
|
};
|
|
1104
|
+
export * from './extended-charts.js';
|
package/dist/code.js
CHANGED
|
@@ -320,7 +320,7 @@ const getMarkupTokenKind = (value, source, start) => {
|
|
|
320
320
|
return 'symbol';
|
|
321
321
|
const openingTagIndex = source.lastIndexOf('<', start);
|
|
322
322
|
const closingTagIndex = source.lastIndexOf('>', start);
|
|
323
|
-
if (openingTagIndex
|
|
323
|
+
if (openingTagIndex <= closingTagIndex)
|
|
324
324
|
return undefined;
|
|
325
325
|
const beforeValue = source
|
|
326
326
|
.slice(openingTagIndex + 1, start)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../src/combobox.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAyBD,yBAAyB;AACzB,eAAO,MAAM,6BAA6B,GAAI,MAAM,WAAW,KAAG,uBAwTjE,CAAA"}
|
package/dist/combobox.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
let nextId = 0;
|
|
2
|
+
const ignoresKeyboard = (event, composing) => event.defaultPrevented || event.isComposing || composing ||
|
|
3
|
+
event.altKey || event.ctrlKey || event.metaKey;
|
|
4
|
+
const filterOption = (item, query, filterHidden, writeHidden) => {
|
|
5
|
+
const label = item.textContent.trim().toLowerCase();
|
|
6
|
+
const value = item.dataset.value?.toLowerCase() ?? '';
|
|
7
|
+
const hidden = Boolean(query) && !label.includes(query) && !value.includes(query);
|
|
8
|
+
if (hidden && !item.hidden) {
|
|
9
|
+
writeHidden(item, true);
|
|
10
|
+
}
|
|
11
|
+
else if (!hidden && filterHidden.has(item)) {
|
|
12
|
+
writeHidden(item, false);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/** Editable combobox. */
|
|
16
|
+
export const createLumenComboboxController = (root) => {
|
|
17
|
+
const input = root.querySelector('input[role="combobox"]');
|
|
18
|
+
const list = root.querySelector('[role="listbox"]');
|
|
19
|
+
const document = root.ownerDocument;
|
|
20
|
+
const view = document.defaultView;
|
|
21
|
+
if (!input || !list || !view)
|
|
22
|
+
return { close: () => undefined, destroy: () => undefined };
|
|
23
|
+
const abort = new view.AbortController();
|
|
24
|
+
const { signal } = abort;
|
|
25
|
+
let active;
|
|
26
|
+
let composing = false;
|
|
27
|
+
let filtering = false;
|
|
28
|
+
let destroyed = false;
|
|
29
|
+
const filterHidden = new Set();
|
|
30
|
+
let resetTimer;
|
|
31
|
+
const eventRoot = root.getRootNode();
|
|
32
|
+
const observerRef = {};
|
|
33
|
+
const preserveVisibility = (records) => {
|
|
34
|
+
for (const record of records) {
|
|
35
|
+
if (record.attributeName === 'hidden') {
|
|
36
|
+
for (const option of filterHidden)
|
|
37
|
+
if (option === record.target)
|
|
38
|
+
filterHidden.delete(option);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const option of filterHidden) {
|
|
42
|
+
if (!list.contains(option)) {
|
|
43
|
+
option.hidden = false;
|
|
44
|
+
filterHidden.delete(option);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
if (!list.id)
|
|
49
|
+
list.id = `ui-combobox-list-${++nextId}`;
|
|
50
|
+
input.setAttribute('aria-controls', list.id);
|
|
51
|
+
input.setAttribute('aria-autocomplete', 'list');
|
|
52
|
+
const items = () => [...list.querySelectorAll('[role="option"]')];
|
|
53
|
+
const editable = () => !input.matches(':disabled') && !input.readOnly;
|
|
54
|
+
const available = (item) => !item.hidden &&
|
|
55
|
+
!item.matches(':disabled, [disabled]') && item.getAttribute('aria-disabled') !== 'true';
|
|
56
|
+
const activate = (item) => {
|
|
57
|
+
active = item;
|
|
58
|
+
for (const option of items())
|
|
59
|
+
option.setAttribute('aria-selected', String(option === item));
|
|
60
|
+
if (item) {
|
|
61
|
+
if (!item.id)
|
|
62
|
+
item.id = `ui-combobox-option-${++nextId}`;
|
|
63
|
+
input.setAttribute('aria-activedescendant', item.id);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
input.removeAttribute('aria-activedescendant');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const close = () => {
|
|
70
|
+
input.setAttribute('aria-expanded', 'false');
|
|
71
|
+
if (!list.hidden)
|
|
72
|
+
list.hidden = true;
|
|
73
|
+
list.dataset.state = 'closed';
|
|
74
|
+
activate();
|
|
75
|
+
};
|
|
76
|
+
const flushVisibility = () => {
|
|
77
|
+
preserveVisibility(observerRef.current?.takeRecords() ?? []);
|
|
78
|
+
};
|
|
79
|
+
const writeHidden = (item, hidden) => {
|
|
80
|
+
flushVisibility();
|
|
81
|
+
if (hidden)
|
|
82
|
+
filterHidden.add(item);
|
|
83
|
+
else
|
|
84
|
+
filterHidden.delete(item);
|
|
85
|
+
item.hidden = hidden;
|
|
86
|
+
preserveVisibility(observerRef.current?.takeRecords().slice(1) ?? []);
|
|
87
|
+
};
|
|
88
|
+
const refresh = () => {
|
|
89
|
+
flushVisibility();
|
|
90
|
+
const query = input.value.trim().toLowerCase();
|
|
91
|
+
for (const item of items()) {
|
|
92
|
+
item.tabIndex = -1;
|
|
93
|
+
if (!item.id)
|
|
94
|
+
item.id = `ui-combobox-option-${++nextId}`;
|
|
95
|
+
if (filtering)
|
|
96
|
+
filterOption(item, query, filterHidden, writeHidden);
|
|
97
|
+
}
|
|
98
|
+
if (active && (!list.contains(active) || !available(active)))
|
|
99
|
+
activate();
|
|
100
|
+
if (!editable())
|
|
101
|
+
close();
|
|
102
|
+
};
|
|
103
|
+
const open = () => {
|
|
104
|
+
if (!editable())
|
|
105
|
+
return;
|
|
106
|
+
refresh();
|
|
107
|
+
input.setAttribute('aria-expanded', 'true');
|
|
108
|
+
list.hidden = false;
|
|
109
|
+
list.dataset.state = 'open';
|
|
110
|
+
};
|
|
111
|
+
const select = (item) => {
|
|
112
|
+
if (!editable() || !list.contains(item) || !available(item))
|
|
113
|
+
return;
|
|
114
|
+
input.value = item.dataset.value ?? item.textContent.trim();
|
|
115
|
+
input.dispatchEvent(new view.Event('input', { bubbles: true }));
|
|
116
|
+
input.dispatchEvent(new view.Event('change', { bubbles: true }));
|
|
117
|
+
input.focus({ preventScroll: true });
|
|
118
|
+
close();
|
|
119
|
+
};
|
|
120
|
+
const navigate = (key) => {
|
|
121
|
+
open();
|
|
122
|
+
const options = items().filter(available);
|
|
123
|
+
const index = active ? options.indexOf(active) : -1;
|
|
124
|
+
let next = (index + 1) % options.length;
|
|
125
|
+
if (key === 'ArrowUp') {
|
|
126
|
+
next = index < 0 ? options.length - 1 : (index - 1 + options.length) % options.length;
|
|
127
|
+
}
|
|
128
|
+
activate(options[next]);
|
|
129
|
+
if (typeof active?.scrollIntoView === 'function')
|
|
130
|
+
active.scrollIntoView({ block: 'nearest' });
|
|
131
|
+
};
|
|
132
|
+
const keydown = (event) => {
|
|
133
|
+
if (ignoresKeyboard(event, composing) || !editable())
|
|
134
|
+
return;
|
|
135
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
navigate(event.key);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
switch (event.key) {
|
|
141
|
+
case 'Escape':
|
|
142
|
+
if (list.hidden)
|
|
143
|
+
return;
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
close();
|
|
146
|
+
break;
|
|
147
|
+
case 'Enter':
|
|
148
|
+
if (list.hidden || !active)
|
|
149
|
+
return;
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
select(active);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const optionFromEvent = (event) => (items().find(option => event.composedPath().includes(option)));
|
|
156
|
+
refresh();
|
|
157
|
+
close();
|
|
158
|
+
input.addEventListener('focus', open, { signal });
|
|
159
|
+
input.addEventListener('compositionstart', () => {
|
|
160
|
+
composing = true;
|
|
161
|
+
}, { signal });
|
|
162
|
+
input.addEventListener('compositionend', () => {
|
|
163
|
+
composing = false;
|
|
164
|
+
filtering = true;
|
|
165
|
+
activate();
|
|
166
|
+
open();
|
|
167
|
+
}, { signal });
|
|
168
|
+
input.addEventListener('input', () => {
|
|
169
|
+
if (composing)
|
|
170
|
+
return;
|
|
171
|
+
filtering = true;
|
|
172
|
+
activate();
|
|
173
|
+
open();
|
|
174
|
+
}, { signal });
|
|
175
|
+
input.addEventListener('keydown', keydown, { signal });
|
|
176
|
+
eventRoot.addEventListener('reset', event => {
|
|
177
|
+
if (event.target !== input.form)
|
|
178
|
+
return;
|
|
179
|
+
view.clearTimeout(resetTimer);
|
|
180
|
+
resetTimer = view.setTimeout(() => {
|
|
181
|
+
if (destroyed || event.defaultPrevented || !input.isConnected)
|
|
182
|
+
return;
|
|
183
|
+
composing = false;
|
|
184
|
+
filtering = true;
|
|
185
|
+
close();
|
|
186
|
+
refresh();
|
|
187
|
+
});
|
|
188
|
+
}, { capture: true, signal });
|
|
189
|
+
list.addEventListener('pointerdown', event => {
|
|
190
|
+
if (optionFromEvent(event))
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
}, { signal });
|
|
193
|
+
list.addEventListener('click', event => {
|
|
194
|
+
if (event.defaultPrevented)
|
|
195
|
+
return;
|
|
196
|
+
const option = optionFromEvent(event);
|
|
197
|
+
if (option) {
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
select(option);
|
|
200
|
+
}
|
|
201
|
+
}, { signal });
|
|
202
|
+
const isNode = (target) => {
|
|
203
|
+
try {
|
|
204
|
+
view.Node.prototype.contains.call(target, null);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
root.addEventListener('focusout', event => {
|
|
212
|
+
if (!event.relatedTarget || !isNode(event.relatedTarget) || !root.contains(event.relatedTarget))
|
|
213
|
+
close();
|
|
214
|
+
}, { signal });
|
|
215
|
+
document.addEventListener('pointerdown', event => {
|
|
216
|
+
if (!event.composedPath().includes(root))
|
|
217
|
+
close();
|
|
218
|
+
}, { signal });
|
|
219
|
+
const observer = new view.MutationObserver(records => {
|
|
220
|
+
preserveVisibility(records);
|
|
221
|
+
if (!destroyed)
|
|
222
|
+
refresh();
|
|
223
|
+
});
|
|
224
|
+
observerRef.current = observer;
|
|
225
|
+
observer.observe(list, {
|
|
226
|
+
attributeFilter: ['disabled', 'aria-disabled', 'data-value', 'hidden'],
|
|
227
|
+
attributes: true,
|
|
228
|
+
characterData: true,
|
|
229
|
+
childList: true,
|
|
230
|
+
subtree: true
|
|
231
|
+
});
|
|
232
|
+
const disabledObserver = new view.MutationObserver(records => {
|
|
233
|
+
if (records.some(record => record.target.nodeName === 'FIELDSET' && record.target.contains(input)))
|
|
234
|
+
refresh();
|
|
235
|
+
});
|
|
236
|
+
disabledObserver.observe(eventRoot, { attributeFilter: ['disabled'], attributes: true, subtree: true });
|
|
237
|
+
observer.observe(input, { attributeFilter: ['disabled', 'readonly'], attributes: true });
|
|
238
|
+
return {
|
|
239
|
+
close,
|
|
240
|
+
destroy: () => {
|
|
241
|
+
destroyed = true;
|
|
242
|
+
view.clearTimeout(resetTimer);
|
|
243
|
+
abort.abort();
|
|
244
|
+
flushVisibility();
|
|
245
|
+
observer.disconnect();
|
|
246
|
+
disabledObserver.disconnect();
|
|
247
|
+
for (const option of filterHidden)
|
|
248
|
+
option.hidden = false;
|
|
249
|
+
filterHidden.clear();
|
|
250
|
+
close();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
};
|