From 274d1a7208f9ce6124b34b135ff57972693d012b Mon Sep 17 00:00:00 2001 From: Evangeline Ireland Date: Mon, 23 Dec 2024 16:07:48 -0800 Subject: [PATCH] 181846570 adjustable row height (#1700) * Adds cell-span styling wo user can add long text * Fixes text styling when row height is greater than the default height * Adds rowHeight as a property of the models * Adds a row divider that can be used as a handle for changing row height * limits the queryselector for the row element to the containing collection * Styles overflow text in a grid cell * Row height changes saves and restores * chore: code review tweaks * chore: fix cypress tests --------- Co-authored-by: Kirk Swenson --- v3/cypress/support/elements/table-tile.ts | 2 +- .../case-table/attribute-value-cell.tsx | 6 +- .../components/case-table/case-table-model.ts | 11 ++- .../case-table/case-table-shared.scss | 3 - .../components/case-table/case-table-types.ts | 7 +- v3/src/components/case-table/case-table.scss | 33 ++++++- .../case-table/collection-table-model.ts | 17 ++-- .../case-table/collection-table.tsx | 13 +-- v3/src/components/case-table/row-divider.tsx | 86 +++++++++++++++++++ v3/src/components/case-table/use-columns.tsx | 10 ++- .../case-table/use-index-column.tsx | 11 ++- v3/src/components/case-table/use-rows.ts | 5 +- .../attribute-format-utils.tsx | 21 +++-- 13 files changed, 188 insertions(+), 37 deletions(-) create mode 100644 v3/src/components/case-table/row-divider.tsx diff --git a/v3/cypress/support/elements/table-tile.ts b/v3/cypress/support/elements/table-tile.ts index 5eac290cbf..162e6846a3 100644 --- a/v3/cypress/support/elements/table-tile.ts +++ b/v3/cypress/support/elements/table-tile.ts @@ -68,7 +68,7 @@ export const TableTileElements = { [data-testid=codap-index-content-button]`) }, openIndexMenuForRow(rowNum: number, collectionIndex = 1) { - this.getIndexCellInRow(rowNum, collectionIndex).click("top") + this.getIndexCellInRow(rowNum, collectionIndex).click() }, getIndexMenu() { return cy.get("[data-testid=index-menu-list]") diff --git a/v3/src/components/case-table/attribute-value-cell.tsx b/v3/src/components/case-table/attribute-value-cell.tsx index 3da07b2e15..1e1ee20464 100644 --- a/v3/src/components/case-table/attribute-value-cell.tsx +++ b/v3/src/components/case-table/attribute-value-cell.tsx @@ -4,9 +4,12 @@ import { useDataSet } from "../../hooks/use-data-set" import { symParent } from "../../models/data/data-set-types" import { symDom, TRenderCellProps } from "./case-table-types" import { renderAttributeValue } from "../case-tile-common/attribute-format-utils" +import { useCollectionTableModel } from "./use-collection-table-model" export function AttributeValueCell({ column, row }: TRenderCellProps) { const { data, metadata } = useDataSet() + const collectionTableModel = useCollectionTableModel() + const rowHeight = collectionTableModel?.rowHeight const strValue = (data?.getStrValue(row.__id__, column.key) ?? "").trim() const numValue = data?.getNumeric(row.__id__, column.key) // if this is the first React render after performance rendering, add a @@ -16,7 +19,8 @@ export function AttributeValueCell({ column, row }: TRenderCellProps) { const isParentCollapsed = row[symParent] ? metadata?.isCollapsed(row[symParent]) : false const { value, content } = isParentCollapsed ? { value: "", content: null } - : renderAttributeValue(strValue, numValue, false, data?.attrFromID(column.key), key) + : renderAttributeValue(strValue, numValue, false, data?.attrFromID(column.key), + key, rowHeight) return ( diff --git a/v3/src/components/case-table/case-table-model.ts b/v3/src/components/case-table/case-table-model.ts index 52e62914aa..20d2d49093 100644 --- a/v3/src/components/case-table/case-table-model.ts +++ b/v3/src/components/case-table/case-table-model.ts @@ -11,6 +11,8 @@ export const CaseTableModel = TileContentModel type: types.optional(types.literal(kCaseTableTileType), kCaseTableTileType), // key is attribute id; value is width columnWidths: types.map(types.number), + // key is collection id, value is row height for collection + rowHeights: types.map(types.number), // Only used for serialization; volatile property used during run time horizontalScrollOffset: 0 }) @@ -36,6 +38,9 @@ export const CaseTableModel = TileContentModel columnWidth(attrId: string) { return self.columnWidths.get(attrId) }, + getRowHeightForCollection(collectionId: string) { + return self.rowHeights.get(collectionId) + } })) .views(self => { const collectionTableModels = new Map() @@ -44,7 +49,8 @@ export const CaseTableModel = TileContentModel getCollectionTableModel(collectionId: string) { let collectionTableModel = collectionTableModels.get(collectionId) if (!collectionTableModel) { - collectionTableModel = new CollectionTableModel(collectionId) + const rowHeight = self.getRowHeightForCollection(collectionId) + collectionTableModel = new CollectionTableModel(collectionId, rowHeight) collectionTableModels.set(collectionId, collectionTableModel) } return collectionTableModel @@ -63,6 +69,9 @@ export const CaseTableModel = TileContentModel setColumnWidths(columnWidths: Map) { self.columnWidths.replace(columnWidths) }, + setRowHeightForCollection(collectionId: string, height: number) { + self.rowHeights.set(collectionId, height) + }, updateAfterSharedModelChanges(sharedModel?: ISharedModel) { // TODO }, diff --git a/v3/src/components/case-table/case-table-shared.scss b/v3/src/components/case-table/case-table-shared.scss index 2eabbeddb4..81d0971d7d 100644 --- a/v3/src/components/case-table/case-table-shared.scss +++ b/v3/src/components/case-table/case-table-shared.scss @@ -1,11 +1,8 @@ $header-row-height: 30; $header-row-height-px: #{$header-row-height}px; -$body-row-height: 18; -$body-row-height-px: #{$body-row-height}px; // https://mattferderer.com/use-sass-variables-in-typescript-and-javascript :export { headerRowHeight: $header-row-height; - bodyRowHeight: $body-row-height; } diff --git a/v3/src/components/case-table/case-table-types.ts b/v3/src/components/case-table/case-table-types.ts index 111cec03b6..1a6e8c8243 100644 --- a/v3/src/components/case-table/case-table-types.ts +++ b/v3/src/components/case-table/case-table-types.ts @@ -2,6 +2,7 @@ import { CalculatedColumn, CellClickArgs, CellKeyDownArgs, CellSelectArgs, ColSpanArgs, Column, RenderCellProps, RenderEditCellProps, Renderers, RenderHeaderCellProps, RenderRowProps, RowsChangeData } from "react-data-grid" +import { DEBUG_CASE_IDS } from "../../lib/debug" import { IGroupedCase, symFirstChild } from "../../models/data/data-set-types" export const kCaseTableIdBase = "case-table" @@ -55,7 +56,7 @@ export type OnScrollRowRangeIntoViewFn = (collectionId: string, rowIndices: numb export const kInputRowKey = "__input__" export const kDropzoneWidth = 30 -export const kIndexColumnWidth = 52 +export const kIndexColumnWidth = DEBUG_CASE_IDS ? 150 : 52 export const kDefaultColumnWidth = 80 export const kMinAutoColumnWidth = 50 export const kMaxAutoColumnWidth = 600 @@ -67,3 +68,7 @@ export const kCaseTableHeaderFont = `bold ${kCaseTableFontSize} ${kCaseTableFont export const kCaseTableBodyFont = `${kCaseTableFontSize} ${kCaseTableFontFamily}` export const kCaseTableDefaultWidth = 580 +export const kDefaultRowHeaderHeight = 30 +export const kDefaultRowHeight = 18 +// used for row resizing +export const kSnapToLineHeight = 14 diff --git a/v3/src/components/case-table/case-table.scss b/v3/src/components/case-table/case-table.scss index 4fdccf095b..ec9ba03218 100644 --- a/v3/src/components/case-table/case-table.scss +++ b/v3/src/components/case-table/case-table.scss @@ -277,7 +277,14 @@ $table-body-font-size: 8pt; } } } - + .codap-index-cell-wrapper { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + position: absolute; + } .rdg-cell { // RDG.beta.17 moved all CSS inside @layers, which decreased their priority // so that some of Chakra's CSS takes precedence, including the gridlines. @@ -323,6 +330,19 @@ $table-body-font-size: 8pt; background-color: var(--rdg-row-selected-background-color); } + &.codap-data-cell.multi-line { + white-space: normal; + overflow-wrap: break-word; + + .cell-span, .rdg-text-editor { + // https://css-tricks.com/almanac/properties/l/line-clamp/ + line-height: 14.5px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + } + } &.codap-data-cell.rowId-__input__ { cursor: text; } @@ -334,8 +354,10 @@ $table-body-font-size: 8pt; height: 14px; } } - + .cell-span { + width: 100%; + height: 100%; &.numeric-format { width: 100%; @@ -393,6 +415,13 @@ $table-body-font-size: 8pt; opacity: 30%; } } + + .codap-row-divider { + height: 9px; + position: absolute; + cursor: row-resize; + z-index: 1; + } } .codap-menu-list { diff --git a/v3/src/components/case-table/collection-table-model.ts b/v3/src/components/case-table/collection-table-model.ts index 21a90b9604..1527e18b38 100644 --- a/v3/src/components/case-table/collection-table-model.ts +++ b/v3/src/components/case-table/collection-table-model.ts @@ -2,10 +2,8 @@ import { action, computed, makeObservable, observable } from "mobx" import { symParent } from "../../models/data/data-set-types" import { getNumericCssVariable } from "../../utilities/css-utils" import { uniqueId } from "../../utilities/js-utils" -import { IScrollOptions, TRow } from "./case-table-types" +import { IScrollOptions, kDefaultRowHeaderHeight, kDefaultRowHeight, TRow } from "./case-table-types" -const kDefaultRowHeaderHeight = 30 -const kDefaultRowHeight = 18 const kDefaultRowCount = 12 const kDefaultGridHeight = kDefaultRowHeaderHeight + (kDefaultRowCount * kDefaultRowHeight) @@ -47,8 +45,11 @@ export class CollectionTableModel { // rows are passed directly to RDG for rendering @observable.shallow rows: TRow[] = [] - constructor(collectionId: string) { + @observable rowHeight + + constructor(collectionId: string, rowHeight = kDefaultRowHeight) { this.collectionId = collectionId + this.rowHeight = rowHeight makeObservable(this) } @@ -61,10 +62,6 @@ export class CollectionTableModel { return getNumericCssVariable(this.element, "--rdg-row-header-height") ?? kDefaultRowHeaderHeight } - get rowHeight() { - return getNumericCssVariable(this.element, "--rdg-row-height") ?? kDefaultRowHeight - } - // visible height of the body of the grid, i.e. the rows excluding the header row get gridBodyHeight() { return (this.element?.getBoundingClientRect().height ?? kDefaultGridHeight) - kDefaultRowHeaderHeight @@ -232,6 +229,10 @@ export class CollectionTableModel { this.inputRowIndex = inputRowIndex } + @action setRowHeight(rowHeight: number) { + this.rowHeight = rowHeight + } + @action syncScrollTopFromEvent(event: React.UIEvent) { const { scrollTop } = event.currentTarget this.lastScrollStep = this.scrollStep diff --git a/v3/src/components/case-table/collection-table.tsx b/v3/src/components/case-table/collection-table.tsx index a84d8f0398..6333ce6665 100644 --- a/v3/src/components/case-table/collection-table.tsx +++ b/v3/src/components/case-table/collection-table.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import DataGrid, { CellKeyboardEvent, DataGridHandle } from "react-data-grid" import { kCollectionTableBodyDropZoneBaseId } from "./case-table-drag-drop" import { - kInputRowKey, OnScrollClosestRowIntoViewFn, OnScrollRowRangeIntoViewFn, OnTableScrollFn, + kDefaultRowHeight, kInputRowKey, OnScrollClosestRowIntoViewFn, OnScrollRowRangeIntoViewFn, OnTableScrollFn, TCellKeyDownArgs, TRenderers, TRow } from "./case-table-types" import { CollectionTableSpacer } from "./collection-table-spacer" @@ -75,6 +75,7 @@ export const CollectionTable = observer(function CollectionTable(props: IProps) const [selectionStartRowIdx, setSelectionStartRowIdx] = useState(null) const initialPointerDownPosition = useRef({ x: 0, y: 0 }) const kPointerMovementThreshold = 3 + const rowHeight = collectionTableModel?.rowHeight ?? kDefaultRowHeight useEffect(function setGridElement() { const element = gridRef.current?.element @@ -279,8 +280,8 @@ export const CollectionTable = observer(function CollectionTable(props: IProps) const startAutoScroll = useCallback((clientY: number) => { const grid = gridRef.current?.element - const rowHeight = collectionTableModel?.rowHeight - if (!grid || !rowHeight) return + const rHeight = collectionTableModel?.rowHeight + if (!grid || !rHeight) return const scrollSpeed = 50 @@ -291,11 +292,11 @@ export const CollectionTable = observer(function CollectionTable(props: IProps) if (mouseY.current < top + kScrollMargin) { grid.scrollTop -= scrollSpeed const scrolledTop = grid.scrollTop - scrolledToRowIdx = Math.floor(scrolledTop / rowHeight) + scrolledToRowIdx = Math.floor(scrolledTop / rHeight) } else if (mouseY.current > bottom - kScrollMargin) { grid.scrollTop += scrollSpeed const scrolledBottom = grid.scrollTop + grid.clientHeight - 1 - scrolledToRowIdx = Math.floor(scrolledBottom / rowHeight) + scrolledToRowIdx = Math.floor(scrolledBottom / rHeight) } if (scrolledToRowIdx != null && selectionStartRowIdx != null && scrolledToRowIdx >= 0 && @@ -371,7 +372,7 @@ export const CollectionTable = observer(function CollectionTable(props: IProps) diff --git a/v3/src/components/case-table/row-divider.tsx b/v3/src/components/case-table/row-divider.tsx new file mode 100644 index 0000000000..e19690e564 --- /dev/null +++ b/v3/src/components/case-table/row-divider.tsx @@ -0,0 +1,86 @@ +import { clsx } from "clsx" +import { observer } from "mobx-react-lite" +import React, { useEffect, useRef, useState } from "react" +import { createPortal } from "react-dom" +import { useCollectionContext } from "../../hooks/use-collection-context" +import { kDefaultRowHeaderHeight, kDefaultRowHeight, kIndexColumnWidth, kSnapToLineHeight } from "./case-table-types" +import { useCaseTableModel } from "./use-case-table-model" +import { useCollectionTableModel } from "./use-collection-table-model" + +const kTableRowDividerHeight = 13 +const kTableDividerOffset = Math.floor(kTableRowDividerHeight / 2) +interface IRowDividerProps { + rowId: string +} +export const RowDivider = observer(function RowDivider({ rowId }: IRowDividerProps) { + const collectionTableModel = useCollectionTableModel() + const collectionId = useCollectionContext() + const collectionTable = collectionTableModel?.element + const caseTableModel = useCaseTableModel() + const rows = collectionTableModel?.rows + const isResizing = useRef(false) + const startY = useRef(0) + const getRowHeight = () => collectionTableModel?.rowHeight ?? kDefaultRowHeight + const initialHeight = useRef(getRowHeight()) + const rowIdx = rows?.findIndex(row => row.__id__ === rowId) + const [rowElement, setRowElement] = useState(null) + const getRowBottom = () => { + if (rowIdx === 0) return getRowHeight() + else return (rowIdx && collectionTableModel?.getRowBottom(rowIdx)) ?? kDefaultRowHeight + } + + + useEffect(() => { + (rowIdx != null && collectionTable) && + setRowElement(collectionTable.querySelector(`[aria-rowindex="${rowIdx + 2}"]`) as HTMLDivElement) + }, [collectionTable, rowIdx]) + + // allow the user to drag the divider + const handleMouseDown = (e: React.MouseEvent) => { + e.stopPropagation() + isResizing.current = true + startY.current = e.clientY + initialHeight.current = getRowHeight() + document.addEventListener("mousemove", handleMouseMove) + document.addEventListener("mouseup", handleMouseUp) + } + + const handleMouseMove = (e: MouseEvent) => { + e.stopPropagation() + if (isResizing.current) { + const deltaY = e.clientY - startY.current + const tempNewHeight = Math.max(kDefaultRowHeight, initialHeight.current + deltaY) + const newHeight = kSnapToLineHeight * Math.round((tempNewHeight - 4) / kSnapToLineHeight) + 4 + collectionTableModel?.setRowHeight(newHeight) + } + } + + const handleMouseUp = (e: MouseEvent) => { + e.stopPropagation() + isResizing.current = false + caseTableModel?.applyModelChange(() => { + caseTableModel?.setRowHeightForCollection(collectionId, getRowHeight()) + }, + { + log: "Change row height", + undoStringKey: "DG.Undo.caseTable.changeRowHeight", + redoStringKey: "DG.Redo.caseTable.changeRowHeight" + } + ) + document.removeEventListener("mousemove", handleMouseMove) + document.removeEventListener("mouseup", handleMouseUp) + } + + const className = clsx("codap-row-divider") + const top = getRowBottom() + kDefaultRowHeaderHeight - kTableDividerOffset + const width = kIndexColumnWidth + return ( + rowElement + ? createPortal(( +
+ ), rowElement) + : null + ) +}) diff --git a/v3/src/components/case-table/use-columns.tsx b/v3/src/components/case-table/use-columns.tsx index 3a859e2242..7007793cb1 100644 --- a/v3/src/components/case-table/use-columns.tsx +++ b/v3/src/components/case-table/use-columns.tsx @@ -9,10 +9,11 @@ import { getCollectionAttrs } from "../../models/data/data-set-utils" import { mstReaction } from "../../utilities/mst-reaction" import { isCaseEditable } from "../../utilities/plugin-utils" import { AttributeValueCell } from "./attribute-value-cell" -import { kDefaultColumnWidth, TColumn } from "./case-table-types" +import { kDefaultColumnWidth, kDefaultRowHeight, TColumn } from "./case-table-types" import CellTextEditor from "./cell-text-editor" import ColorCellTextEditor from "./color-cell-text-editor" import { ColumnHeader } from "./column-header" +import { useCollectionTableModel } from "./use-collection-table-model" interface IUseColumnsProps { data?: IDataSet @@ -20,9 +21,11 @@ interface IUseColumnsProps { } export const useColumns = ({ data, indexColumn }: IUseColumnsProps) => { const caseMetadata = useCaseMetadata() + const collectionTableModel = useCollectionTableModel() const parentCollection = useParentCollectionContext() const collectionId = useCollectionContext() const [columns, setColumns] = useState([]) + const rowHeight = collectionTableModel?.rowHeight ?? kDefaultRowHeight useEffect(() => { // rebuild column definitions when referenced properties change @@ -49,7 +52,8 @@ export const useColumns = ({ data, indexColumn }: IUseColumnsProps) => { resizable: true, headerCellClass: `codap-column-header`, renderHeaderCell: ColumnHeader, - cellClass: row => clsx("codap-data-cell", `rowId-${row.__id__}`, {"formula-column": hasFormula}), + cellClass: row => clsx("codap-data-cell", `rowId-${row.__id__}`, + {"formula-column": hasFormula, "multi-line": rowHeight > kDefaultRowHeight}), renderCell: AttributeValueCell, editable: row => isCaseEditable(data, row.__id__), renderEditCell: isEditable @@ -64,7 +68,7 @@ export const useColumns = ({ data, indexColumn }: IUseColumnsProps) => { }, { name: "useColumns [rebuild columns]", equals: comparer.structural, fireImmediately: true }, data ) - }, [caseMetadata, collectionId, data, indexColumn, parentCollection]) + }, [caseMetadata, collectionId, data, indexColumn, parentCollection, rowHeight]) return columns } diff --git a/v3/src/components/case-table/use-index-column.tsx b/v3/src/components/case-table/use-index-column.tsx index ec4bf16323..8c0796ebbc 100644 --- a/v3/src/components/case-table/use-index-column.tsx +++ b/v3/src/components/case-table/use-index-column.tsx @@ -16,13 +16,12 @@ import { t } from "../../utilities/translation/translate" import { kIndexColumnKey } from "../case-tile-common/case-tile-types" import { IndexMenuList } from "../case-tile-common/index-menu-list" import { useParentChildFocusRedirect } from "../case-tile-common/use-parent-child-focus-redirect" -import { kInputRowKey, TColSpanArgs, TColumn, TRenderCellProps } from "./case-table-types" +import { kIndexColumnWidth, kInputRowKey, TColSpanArgs, TColumn, TRenderCellProps } from "./case-table-types" import { ColumnHeader } from "./column-header" +import { RowDivider } from "./row-divider" import DragIndicator from "../../assets/icons/drag-indicator.svg" -const kIndexColumnWidth = DEBUG_CASE_IDS ? 150 : 52 - interface IColSpanProps { data?: IDataSet metadata?: ISharedCaseMetadata @@ -55,6 +54,7 @@ export const useIndexColumn = () => { ? data.caseInfoMap.get(parentId)?.childCaseIds ?? [] : [] const collapsedCaseCount = collapsedCases.length + const isInputRow = __id__ === kInputRowKey function handleClick(e: React.MouseEvent) { if (parentId && collapsedCaseCount) { @@ -71,8 +71,11 @@ export const useIndexColumn = () => { } return ( - + + {(!isInputRow) && } +
) }, [caseMetadata, data, disableMenu]) const indexColumn = useRef() diff --git a/v3/src/components/case-table/use-rows.ts b/v3/src/components/case-table/use-rows.ts index 6aec03771d..8bb9ed7858 100644 --- a/v3/src/components/case-table/use-rows.ts +++ b/v3/src/components/case-table/use-rows.ts @@ -77,6 +77,7 @@ export const useRows = (gridElement: HTMLDivElement | null) => { domRows?.forEach(row => { const rowIndex = Number(row.getAttribute("aria-rowindex")) - 2 const caseId = collection?.caseIds[rowIndex] + const rowHeight = collectionTableModel?.rowHeight const cells = row.querySelectorAll(".rdg-cell") cells.forEach(cell => { const colIndex = Number(cell.getAttribute("aria-colindex")) - 2 @@ -85,14 +86,14 @@ export const useRows = (gridElement: HTMLDivElement | null) => { if (data && caseId && attr && cellSpan) { const strValue = data.getStrValue(caseId, attr.id) const numValue = data.getNumeric(caseId, attr.id) - const { value } = renderAttributeValue(strValue, numValue, false, attr) + const { value } = renderAttributeValue(strValue, numValue, false, attr, rowHeight) cellSpan.textContent = value setCachedDomAttr(caseId, attr.id) } }) }) }) - }, [collectionId, data, gridElement, setCachedDomAttr]) + }, [collectionId, collectionTableModel, data, gridElement, setCachedDomAttr]) const resetRowCacheAndSyncRows = useDebouncedCallback(() => { resetRowCache() diff --git a/v3/src/components/case-tile-common/attribute-format-utils.tsx b/v3/src/components/case-tile-common/attribute-format-utils.tsx index 3c903b7c52..0de243cd3e 100644 --- a/v3/src/components/case-tile-common/attribute-format-utils.tsx +++ b/v3/src/components/case-tile-common/attribute-format-utils.tsx @@ -9,8 +9,10 @@ import { parseColor } from "../../utilities/color-utils" import { isStdISODateString } from "../../utilities/date-iso-utils" import { parseDate } from "../../utilities/date-parser" import { formatDate } from "../../utilities/date-utils" -import { kCaseTableBodyFont, kCaseTableHeaderFont, kMaxAutoColumnWidth, - kMinAutoColumnWidth } from "../case-table/case-table-types" +import { + kCaseTableBodyFont, kCaseTableHeaderFont, kDefaultRowHeight, + kMaxAutoColumnWidth, kMinAutoColumnWidth, kSnapToLineHeight +} from "../case-table/case-table-types" // cache d3 number formatters so we don't have to generate them on every render type TNumberFormatter = (n: number) => string @@ -25,9 +27,14 @@ export const getNumFormatter = (formatStr: string) => { return formatter } -export function renderAttributeValue(str = "", num = NaN, showUnits = false, attr?: IAttribute, key?: number) { +export function renderAttributeValue(str = "", num = NaN, showUnits = false, attr?: IAttribute, + key?: number, rowHeight: number = kDefaultRowHeight) { const { type, userType, numPrecision, datePrecision } = attr || {} let formatClass = "" + // https://css-tricks.com/almanac/properties/l/line-clamp/ + const lineClamp = rowHeight > kDefaultRowHeight + ? Math.ceil(rowHeight / (kSnapToLineHeight + 1)) + : 0 // boundaries if (type === "boundary") { @@ -82,7 +89,9 @@ export function renderAttributeValue(str = "", num = NaN, showUnits = false, att const formattedDate = formatDate(date, datePrecision) return { value: str, - content: {formattedDate || `"${str}"`} + content: + {formattedDate || `"${str}"`} + } } else { // If the date is not valid, wrap it in quotes (CODAP V2 behavior). @@ -92,7 +101,9 @@ export function renderAttributeValue(str = "", num = NaN, showUnits = false, att return { value: str, - content: {str} + content: + {str} + } }