@lensui/lens-table 0.1.75 → 0.1.77

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/index.cjs CHANGED
@@ -335,12 +335,21 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
335
335
  function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownHeight, style, labels, onChange, onCommit, onCancel }) {
336
336
  const editorRef = (0, import_react2.useRef)(null);
337
337
  const popupRef = (0, import_react2.useRef)(null);
338
+ const activeOptionRef = (0, import_react2.useRef)(null);
339
+ const popupReadyRef = (0, import_react2.useRef)(false);
340
+ const navigationSourceRef = (0, import_react2.useRef)(null);
338
341
  const originalValueRef = (0, import_react2.useRef)(value);
339
342
  const listboxId = (0, import_react2.useId)();
340
343
  const originalValue = originalValueRef.current;
341
344
  const [inputValue, setInputValue] = (0, import_react2.useState)(value);
342
- const [activeIndex, setActiveIndex] = (0, import_react2.useState)(0);
343
- const [popupStyle, setPopupStyle] = (0, import_react2.useState)({});
345
+ const [activeIndex, setActiveIndex] = (0, import_react2.useState)(() => {
346
+ const selectedIndex = options.findIndex((option) => option.value === value);
347
+ return selectedIndex >= 0 ? selectedIndex : 0;
348
+ });
349
+ const [popupStyle, setPopupStyle] = (0, import_react2.useState)({
350
+ position: "fixed",
351
+ visibility: "hidden"
352
+ });
344
353
  const filteredOptions = (0, import_react2.useMemo)(() => {
345
354
  const query = inputValue.trim().toLocaleLowerCase();
346
355
  if (!query || query === originalValue.toLocaleLowerCase()) return options;
@@ -358,7 +367,8 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
358
367
  bottom: window.innerHeight - rect.top + 4,
359
368
  left: rect.left,
360
369
  width: rect.width,
361
- maxHeight: Math.min(maxDropdownHeight, availableAbove)
370
+ maxHeight: Math.min(maxDropdownHeight, availableAbove),
371
+ visibility: popupReadyRef.current ? "visible" : "hidden"
362
372
  });
363
373
  return;
364
374
  }
@@ -367,7 +377,8 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
367
377
  top: rect.bottom + 4,
368
378
  left: rect.left,
369
379
  width: rect.width,
370
- maxHeight: Math.min(maxDropdownHeight, Math.max(80, availableBelow))
380
+ maxHeight: Math.min(maxDropdownHeight, Math.max(80, availableBelow)),
381
+ visibility: popupReadyRef.current ? "visible" : "hidden"
371
382
  });
372
383
  };
373
384
  positionPopup();
@@ -378,6 +389,23 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
378
389
  window.removeEventListener("scroll", positionPopup, true);
379
390
  };
380
391
  }, [maxDropdownHeight]);
392
+ (0, import_react2.useLayoutEffect)(() => {
393
+ if (popupReadyRef.current) return;
394
+ if (popupStyle.top === void 0 && popupStyle.bottom === void 0) return;
395
+ const selectedOption = popupRef.current?.querySelector('[aria-selected="true"]') ?? activeOptionRef.current;
396
+ selectedOption?.scrollIntoView?.({ block: "center", inline: "nearest" });
397
+ popupReadyRef.current = true;
398
+ setPopupStyle((current) => ({ ...current, visibility: "visible" }));
399
+ }, [popupStyle.bottom, popupStyle.top]);
400
+ (0, import_react2.useLayoutEffect)(() => {
401
+ if (!popupReadyRef.current) return;
402
+ if (navigationSourceRef.current === "keyboard") {
403
+ activeOptionRef.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
404
+ } else if (navigationSourceRef.current === "filter" && popupRef.current) {
405
+ popupRef.current.scrollTop = 0;
406
+ }
407
+ navigationSourceRef.current = null;
408
+ }, [activeIndex, filteredOptions]);
381
409
  (0, import_react2.useEffect)(() => {
382
410
  const handleOutsidePointer = (event) => {
383
411
  const target = event.target;
@@ -397,10 +425,12 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
397
425
  if (event.key === "Escape") onCancel();
398
426
  if (event.key === "ArrowDown") {
399
427
  event.preventDefault();
428
+ navigationSourceRef.current = "keyboard";
400
429
  setActiveIndex((index) => Math.min(filteredOptions.length - 1, index + 1));
401
430
  }
402
431
  if (event.key === "ArrowUp") {
403
432
  event.preventDefault();
433
+ navigationSourceRef.current = "keyboard";
404
434
  setActiveIndex((index) => Math.max(0, index - 1));
405
435
  }
406
436
  if (event.key === "Enter") {
@@ -423,6 +453,7 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
423
453
  onChange: (event) => {
424
454
  setInputValue(event.target.value);
425
455
  onChange(event.target.value);
456
+ navigationSourceRef.current = "filter";
426
457
  setActiveIndex(0);
427
458
  },
428
459
  onKeyDown: handleKeyDown
@@ -432,12 +463,16 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
432
463
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_portal.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: popupRef, id: listboxId, role: "listbox", className: "rvg-choice-content rvg-searchable-choice-content", style: popupStyle, children: filteredOptions.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
433
464
  "button",
434
465
  {
466
+ ref: index === activeIndex ? activeOptionRef : void 0,
435
467
  type: "button",
436
468
  role: "option",
437
469
  "aria-selected": option.value === originalValue,
438
470
  className: `rvg-choice-item${index === activeIndex ? " is-active" : ""}${option.value === originalValue ? " is-selected" : ""}`,
439
471
  onPointerDown: (event) => event.preventDefault(),
440
- onMouseEnter: () => setActiveIndex(index),
472
+ onMouseEnter: () => {
473
+ navigationSourceRef.current = "pointer";
474
+ setActiveIndex(index);
475
+ },
441
476
  onClick: () => selectOption(option),
442
477
  children: [
443
478
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: option.label }),
@@ -1106,6 +1141,7 @@ function paintGrid(options) {
1106
1141
  const headerLeafTop = options.headerLeafTop ?? 0;
1107
1142
  const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
1108
1143
  const colors = { ...COLORS, ...options.colors };
1144
+ const resolveColor = options.resolveColor ?? ((color) => color);
1109
1145
  const getAxisSelectionFill = (rowIndex, columnKey) => selectedColumnKeys.has(columnKey) && striped && rowIndex % 2 === 1 ? colors.axisSelectionStripeFill : colors.axisSelectionFill;
1110
1146
  const bodyTop = options.bodyTop ?? headerHeight;
1111
1147
  const getCellSpan = (rowIndex, columnIndex) => options.cellSpans.get(getCellCoordKey(rowIndex, columnIndex));
@@ -1169,8 +1205,9 @@ function paintGrid(options) {
1169
1205
  const trailingRowStyle = options.rowStyle?.(row, rowIndex);
1170
1206
  const y = bodyTop + rowIndex * rowHeight - scrollTop + getRowDragOffset(rowIndex);
1171
1207
  const paintTrailingLayer = (fill) => {
1172
- if (!fill) return;
1173
- ctx.fillStyle = fill;
1208
+ const resolvedFill = resolveColor(fill);
1209
+ if (!resolvedFill) return;
1210
+ ctx.fillStyle = resolvedFill;
1174
1211
  ctx.fillRect(trailingBlankStart, y, width - trailingBlankStart, rowHeight);
1175
1212
  };
1176
1213
  if (striped && rowIndex % 2 === 1) paintTrailingLayer(colors.stripe);
@@ -1213,8 +1250,11 @@ function paintGrid(options) {
1213
1250
  ctx.fillRect(x, y, cellWidth, cellHeight);
1214
1251
  }
1215
1252
  if (rowStyle?.backgroundColor) {
1216
- ctx.fillStyle = rowStyle.backgroundColor;
1217
- ctx.fillRect(x, y, cellWidth, cellHeight);
1253
+ const rowBackground = resolveColor(rowStyle.backgroundColor);
1254
+ if (rowBackground) {
1255
+ ctx.fillStyle = rowBackground;
1256
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1257
+ }
1218
1258
  }
1219
1259
  if (isAxisSelected) {
1220
1260
  ctx.fillStyle = getAxisSelectionFill(rowIndex, column.key);
@@ -1233,8 +1273,11 @@ function paintGrid(options) {
1233
1273
  const rawValue = column.dataIndex === void 0 ? void 0 : row[column.dataIndex];
1234
1274
  const cellStyle = column.cellStyle?.(rawValue, row, rowIndex);
1235
1275
  if (!isSelectedCell && !isRangeCell && cellStyle?.backgroundColor) {
1236
- ctx.fillStyle = cellStyle.backgroundColor;
1237
- ctx.fillRect(x, y, cellWidth, cellHeight);
1276
+ const cellBackground = resolveColor(cellStyle.backgroundColor);
1277
+ if (cellBackground) {
1278
+ ctx.fillStyle = cellBackground;
1279
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1280
+ }
1238
1281
  }
1239
1282
  if (highlightEditedCells && !insertedRowKeys.has(rowKey) && !isSelectedCell && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`)) {
1240
1283
  ctx.fillStyle = colors.editedFill;
@@ -1245,8 +1288,11 @@ function paintGrid(options) {
1245
1288
  ctx.fillRect(x, y, cellWidth, cellHeight);
1246
1289
  }
1247
1290
  if (annotation?.type === "background") {
1248
- ctx.fillStyle = annotation.color;
1249
- ctx.fillRect(x, y, cellWidth, cellHeight);
1291
+ const annotationBackground = resolveColor(annotation.color);
1292
+ if (annotationBackground) {
1293
+ ctx.fillStyle = annotationBackground;
1294
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1295
+ }
1250
1296
  }
1251
1297
  if (isColumnDropTarget) {
1252
1298
  ctx.fillStyle = isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : colors.columnDropTargetFill;
@@ -1262,14 +1308,14 @@ function paintGrid(options) {
1262
1308
  }
1263
1309
  if (rowSpan > 1) {
1264
1310
  const mergedFill = isColumnDropTarget ? isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1265
- ctx.fillStyle = mergedFill;
1311
+ ctx.fillStyle = resolveColor(mergedFill) ?? colors.background;
1266
1312
  for (let offset = 1; offset < rowSpan; offset += 1) {
1267
1313
  ctx.fillRect(x, y + offset * rowHeight - 1, cellWidth, 2);
1268
1314
  }
1269
1315
  }
1270
1316
  if (colSpan > 1 && !verticalBorderless) {
1271
1317
  const mergedFill = isColumnDropTarget ? isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1272
- ctx.fillStyle = mergedFill;
1318
+ ctx.fillStyle = resolveColor(mergedFill) ?? colors.background;
1273
1319
  let offsetLeft = 0;
1274
1320
  for (let offset = 1; offset < colSpan; offset += 1) {
1275
1321
  offsetLeft += metrics[columnIndex + offset - 1]?.width ?? 0;
@@ -1365,7 +1411,8 @@ function paintGrid(options) {
1365
1411
  ctx.beginPath();
1366
1412
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1367
1413
  ctx.clip();
1368
- ctx.fillStyle = isAxisSelected ? colors.axisSelectionText : isColumnDropTarget ? colors.text : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1414
+ const textColor = isAxisSelected ? colors.axisSelectionText : isColumnDropTarget ? colors.text : resolveColor(cellStyle?.color ?? rowStyle?.color) ?? colors.text;
1415
+ ctx.fillStyle = textColor;
1369
1416
  ctx.font = createCellFont(bodyFontSize);
1370
1417
  ctx.textBaseline = "alphabetic";
1371
1418
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
@@ -1375,7 +1422,7 @@ function paintGrid(options) {
1375
1422
  }
1376
1423
  if (annotation?.type === "corner") {
1377
1424
  const cornerTop = y - 1;
1378
- ctx.fillStyle = annotation.color;
1425
+ ctx.fillStyle = resolveColor(annotation.color) ?? colors.text;
1379
1426
  ctx.beginPath();
1380
1427
  ctx.moveTo(x + cellWidth - 10, cornerTop);
1381
1428
  ctx.lineTo(x + cellWidth, cornerTop);
@@ -1761,6 +1808,18 @@ function readThemePixel(styles, name, fallback) {
1761
1808
  const value = Number.parseFloat(styles.getPropertyValue(name));
1762
1809
  return Number.isFinite(value) && value > 0 ? value : fallback;
1763
1810
  }
1811
+ function resolveCanvasColor(styles, color) {
1812
+ if (!color) return void 0;
1813
+ let resolved = color.trim();
1814
+ for (let depth = 0; depth < 8; depth += 1) {
1815
+ const variable = resolved.match(/^var\(\s*(--[^,\s)]+)\s*(?:,\s*(.+))?\)$/);
1816
+ if (!variable) return resolved || void 0;
1817
+ const value = styles.getPropertyValue(variable[1]).trim();
1818
+ resolved = value || variable[2]?.trim() || "";
1819
+ if (!resolved) return void 0;
1820
+ }
1821
+ return void 0;
1822
+ }
1764
1823
  function getDefaultRowKey(row, index) {
1765
1824
  const id = row.id;
1766
1825
  return typeof id === "string" || typeof id === "number" ? id : index;
@@ -2582,7 +2641,7 @@ function TableInner({
2582
2641
  const scroll = scrollRef.current;
2583
2642
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2584
2643
  const range = isVirtualized ? getViewportRange(scroll.left, rowScrollTop, viewport.width, bodyViewportHeight, rows.length, rowHeight, metrics, virtualOverscan) : { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
2585
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, rowStyle, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2644
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, rowStyle, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, resolveColor: (color) => resolveCanvasColor(themeStyles, color), colors: themeColors });
2586
2645
  }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDragPreview, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rowStyle, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2587
2646
  const scheduleDraw = (0, import_react8.useCallback)(() => {
2588
2647
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
package/dist/index.js CHANGED
@@ -305,12 +305,21 @@ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
305
305
  function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownHeight, style, labels, onChange, onCommit, onCancel }) {
306
306
  const editorRef = useRef(null);
307
307
  const popupRef = useRef(null);
308
+ const activeOptionRef = useRef(null);
309
+ const popupReadyRef = useRef(false);
310
+ const navigationSourceRef = useRef(null);
308
311
  const originalValueRef = useRef(value);
309
312
  const listboxId = useId2();
310
313
  const originalValue = originalValueRef.current;
311
314
  const [inputValue, setInputValue] = useState(value);
312
- const [activeIndex, setActiveIndex] = useState(0);
313
- const [popupStyle, setPopupStyle] = useState({});
315
+ const [activeIndex, setActiveIndex] = useState(() => {
316
+ const selectedIndex = options.findIndex((option) => option.value === value);
317
+ return selectedIndex >= 0 ? selectedIndex : 0;
318
+ });
319
+ const [popupStyle, setPopupStyle] = useState({
320
+ position: "fixed",
321
+ visibility: "hidden"
322
+ });
314
323
  const filteredOptions = useMemo(() => {
315
324
  const query = inputValue.trim().toLocaleLowerCase();
316
325
  if (!query || query === originalValue.toLocaleLowerCase()) return options;
@@ -328,7 +337,8 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
328
337
  bottom: window.innerHeight - rect.top + 4,
329
338
  left: rect.left,
330
339
  width: rect.width,
331
- maxHeight: Math.min(maxDropdownHeight, availableAbove)
340
+ maxHeight: Math.min(maxDropdownHeight, availableAbove),
341
+ visibility: popupReadyRef.current ? "visible" : "hidden"
332
342
  });
333
343
  return;
334
344
  }
@@ -337,7 +347,8 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
337
347
  top: rect.bottom + 4,
338
348
  left: rect.left,
339
349
  width: rect.width,
340
- maxHeight: Math.min(maxDropdownHeight, Math.max(80, availableBelow))
350
+ maxHeight: Math.min(maxDropdownHeight, Math.max(80, availableBelow)),
351
+ visibility: popupReadyRef.current ? "visible" : "hidden"
341
352
  });
342
353
  };
343
354
  positionPopup();
@@ -348,6 +359,23 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
348
359
  window.removeEventListener("scroll", positionPopup, true);
349
360
  };
350
361
  }, [maxDropdownHeight]);
362
+ useLayoutEffect(() => {
363
+ if (popupReadyRef.current) return;
364
+ if (popupStyle.top === void 0 && popupStyle.bottom === void 0) return;
365
+ const selectedOption = popupRef.current?.querySelector('[aria-selected="true"]') ?? activeOptionRef.current;
366
+ selectedOption?.scrollIntoView?.({ block: "center", inline: "nearest" });
367
+ popupReadyRef.current = true;
368
+ setPopupStyle((current) => ({ ...current, visibility: "visible" }));
369
+ }, [popupStyle.bottom, popupStyle.top]);
370
+ useLayoutEffect(() => {
371
+ if (!popupReadyRef.current) return;
372
+ if (navigationSourceRef.current === "keyboard") {
373
+ activeOptionRef.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
374
+ } else if (navigationSourceRef.current === "filter" && popupRef.current) {
375
+ popupRef.current.scrollTop = 0;
376
+ }
377
+ navigationSourceRef.current = null;
378
+ }, [activeIndex, filteredOptions]);
351
379
  useEffect(() => {
352
380
  const handleOutsidePointer = (event) => {
353
381
  const target = event.target;
@@ -367,10 +395,12 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
367
395
  if (event.key === "Escape") onCancel();
368
396
  if (event.key === "ArrowDown") {
369
397
  event.preventDefault();
398
+ navigationSourceRef.current = "keyboard";
370
399
  setActiveIndex((index) => Math.min(filteredOptions.length - 1, index + 1));
371
400
  }
372
401
  if (event.key === "ArrowUp") {
373
402
  event.preventDefault();
403
+ navigationSourceRef.current = "keyboard";
374
404
  setActiveIndex((index) => Math.max(0, index - 1));
375
405
  }
376
406
  if (event.key === "Enter") {
@@ -393,6 +423,7 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
393
423
  onChange: (event) => {
394
424
  setInputValue(event.target.value);
395
425
  onChange(event.target.value);
426
+ navigationSourceRef.current = "filter";
396
427
  setActiveIndex(0);
397
428
  },
398
429
  onKeyDown: handleKeyDown
@@ -402,12 +433,16 @@ function SearchableChoiceEditor({ value, options, allowCustomValue, maxDropdownH
402
433
  /* @__PURE__ */ jsx3(Portal, { children: /* @__PURE__ */ jsx3("div", { ref: popupRef, id: listboxId, role: "listbox", className: "rvg-choice-content rvg-searchable-choice-content", style: popupStyle, children: filteredOptions.map((option, index) => /* @__PURE__ */ jsxs3(
403
434
  "button",
404
435
  {
436
+ ref: index === activeIndex ? activeOptionRef : void 0,
405
437
  type: "button",
406
438
  role: "option",
407
439
  "aria-selected": option.value === originalValue,
408
440
  className: `rvg-choice-item${index === activeIndex ? " is-active" : ""}${option.value === originalValue ? " is-selected" : ""}`,
409
441
  onPointerDown: (event) => event.preventDefault(),
410
- onMouseEnter: () => setActiveIndex(index),
442
+ onMouseEnter: () => {
443
+ navigationSourceRef.current = "pointer";
444
+ setActiveIndex(index);
445
+ },
411
446
  onClick: () => selectOption(option),
412
447
  children: [
413
448
  /* @__PURE__ */ jsx3("span", { children: option.label }),
@@ -1076,6 +1111,7 @@ function paintGrid(options) {
1076
1111
  const headerLeafTop = options.headerLeafTop ?? 0;
1077
1112
  const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
1078
1113
  const colors = { ...COLORS, ...options.colors };
1114
+ const resolveColor = options.resolveColor ?? ((color) => color);
1079
1115
  const getAxisSelectionFill = (rowIndex, columnKey) => selectedColumnKeys.has(columnKey) && striped && rowIndex % 2 === 1 ? colors.axisSelectionStripeFill : colors.axisSelectionFill;
1080
1116
  const bodyTop = options.bodyTop ?? headerHeight;
1081
1117
  const getCellSpan = (rowIndex, columnIndex) => options.cellSpans.get(getCellCoordKey(rowIndex, columnIndex));
@@ -1139,8 +1175,9 @@ function paintGrid(options) {
1139
1175
  const trailingRowStyle = options.rowStyle?.(row, rowIndex);
1140
1176
  const y = bodyTop + rowIndex * rowHeight - scrollTop + getRowDragOffset(rowIndex);
1141
1177
  const paintTrailingLayer = (fill) => {
1142
- if (!fill) return;
1143
- ctx.fillStyle = fill;
1178
+ const resolvedFill = resolveColor(fill);
1179
+ if (!resolvedFill) return;
1180
+ ctx.fillStyle = resolvedFill;
1144
1181
  ctx.fillRect(trailingBlankStart, y, width - trailingBlankStart, rowHeight);
1145
1182
  };
1146
1183
  if (striped && rowIndex % 2 === 1) paintTrailingLayer(colors.stripe);
@@ -1183,8 +1220,11 @@ function paintGrid(options) {
1183
1220
  ctx.fillRect(x, y, cellWidth, cellHeight);
1184
1221
  }
1185
1222
  if (rowStyle?.backgroundColor) {
1186
- ctx.fillStyle = rowStyle.backgroundColor;
1187
- ctx.fillRect(x, y, cellWidth, cellHeight);
1223
+ const rowBackground = resolveColor(rowStyle.backgroundColor);
1224
+ if (rowBackground) {
1225
+ ctx.fillStyle = rowBackground;
1226
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1227
+ }
1188
1228
  }
1189
1229
  if (isAxisSelected) {
1190
1230
  ctx.fillStyle = getAxisSelectionFill(rowIndex, column.key);
@@ -1203,8 +1243,11 @@ function paintGrid(options) {
1203
1243
  const rawValue = column.dataIndex === void 0 ? void 0 : row[column.dataIndex];
1204
1244
  const cellStyle = column.cellStyle?.(rawValue, row, rowIndex);
1205
1245
  if (!isSelectedCell && !isRangeCell && cellStyle?.backgroundColor) {
1206
- ctx.fillStyle = cellStyle.backgroundColor;
1207
- ctx.fillRect(x, y, cellWidth, cellHeight);
1246
+ const cellBackground = resolveColor(cellStyle.backgroundColor);
1247
+ if (cellBackground) {
1248
+ ctx.fillStyle = cellBackground;
1249
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1250
+ }
1208
1251
  }
1209
1252
  if (highlightEditedCells && !insertedRowKeys.has(rowKey) && !isSelectedCell && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`)) {
1210
1253
  ctx.fillStyle = colors.editedFill;
@@ -1215,8 +1258,11 @@ function paintGrid(options) {
1215
1258
  ctx.fillRect(x, y, cellWidth, cellHeight);
1216
1259
  }
1217
1260
  if (annotation?.type === "background") {
1218
- ctx.fillStyle = annotation.color;
1219
- ctx.fillRect(x, y, cellWidth, cellHeight);
1261
+ const annotationBackground = resolveColor(annotation.color);
1262
+ if (annotationBackground) {
1263
+ ctx.fillStyle = annotationBackground;
1264
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1265
+ }
1220
1266
  }
1221
1267
  if (isColumnDropTarget) {
1222
1268
  ctx.fillStyle = isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : colors.columnDropTargetFill;
@@ -1232,14 +1278,14 @@ function paintGrid(options) {
1232
1278
  }
1233
1279
  if (rowSpan > 1) {
1234
1280
  const mergedFill = isColumnDropTarget ? isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1235
- ctx.fillStyle = mergedFill;
1281
+ ctx.fillStyle = resolveColor(mergedFill) ?? colors.background;
1236
1282
  for (let offset = 1; offset < rowSpan; offset += 1) {
1237
1283
  ctx.fillRect(x, y + offset * rowHeight - 1, cellWidth, 2);
1238
1284
  }
1239
1285
  }
1240
1286
  if (colSpan > 1 && !verticalBorderless) {
1241
1287
  const mergedFill = isColumnDropTarget ? isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : colors.columnDropTargetFill : annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? getAxisSelectionFill(rowIndex, column.key) : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1242
- ctx.fillStyle = mergedFill;
1288
+ ctx.fillStyle = resolveColor(mergedFill) ?? colors.background;
1243
1289
  let offsetLeft = 0;
1244
1290
  for (let offset = 1; offset < colSpan; offset += 1) {
1245
1291
  offsetLeft += metrics[columnIndex + offset - 1]?.width ?? 0;
@@ -1335,7 +1381,8 @@ function paintGrid(options) {
1335
1381
  ctx.beginPath();
1336
1382
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1337
1383
  ctx.clip();
1338
- ctx.fillStyle = isAxisSelected ? colors.axisSelectionText : isColumnDropTarget ? colors.text : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1384
+ const textColor = isAxisSelected ? colors.axisSelectionText : isColumnDropTarget ? colors.text : resolveColor(cellStyle?.color ?? rowStyle?.color) ?? colors.text;
1385
+ ctx.fillStyle = textColor;
1339
1386
  ctx.font = createCellFont(bodyFontSize);
1340
1387
  ctx.textBaseline = "alphabetic";
1341
1388
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
@@ -1345,7 +1392,7 @@ function paintGrid(options) {
1345
1392
  }
1346
1393
  if (annotation?.type === "corner") {
1347
1394
  const cornerTop = y - 1;
1348
- ctx.fillStyle = annotation.color;
1395
+ ctx.fillStyle = resolveColor(annotation.color) ?? colors.text;
1349
1396
  ctx.beginPath();
1350
1397
  ctx.moveTo(x + cellWidth - 10, cornerTop);
1351
1398
  ctx.lineTo(x + cellWidth, cornerTop);
@@ -1731,6 +1778,18 @@ function readThemePixel(styles, name, fallback) {
1731
1778
  const value = Number.parseFloat(styles.getPropertyValue(name));
1732
1779
  return Number.isFinite(value) && value > 0 ? value : fallback;
1733
1780
  }
1781
+ function resolveCanvasColor(styles, color) {
1782
+ if (!color) return void 0;
1783
+ let resolved = color.trim();
1784
+ for (let depth = 0; depth < 8; depth += 1) {
1785
+ const variable = resolved.match(/^var\(\s*(--[^,\s)]+)\s*(?:,\s*(.+))?\)$/);
1786
+ if (!variable) return resolved || void 0;
1787
+ const value = styles.getPropertyValue(variable[1]).trim();
1788
+ resolved = value || variable[2]?.trim() || "";
1789
+ if (!resolved) return void 0;
1790
+ }
1791
+ return void 0;
1792
+ }
1734
1793
  function getDefaultRowKey(row, index) {
1735
1794
  const id = row.id;
1736
1795
  return typeof id === "string" || typeof id === "number" ? id : index;
@@ -2552,7 +2611,7 @@ function TableInner({
2552
2611
  const scroll = scrollRef.current;
2553
2612
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2554
2613
  const range = isVirtualized ? getViewportRange(scroll.left, rowScrollTop, viewport.width, bodyViewportHeight, rows.length, rowHeight, metrics, virtualOverscan) : { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
2555
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, rowStyle, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2614
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, rowStyle, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, resolveColor: (color) => resolveCanvasColor(themeStyles, color), colors: themeColors });
2556
2615
  }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDragPreview, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rowStyle, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2557
2616
  const scheduleDraw = useCallback2(() => {
2558
2617
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",