UNPKG

@adaptabletools/adaptable

Version:

Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

249 lines (248 loc) 9.7 kB
import * as React from 'react'; import { IconComponent, isAdaptableSystemIcon, isAdaptableCustomIcon, isAdaptableElementIcon, } from '../../components/Icon'; import { renderWithAdaptableContext } from '../../View/renderWithAdaptableContext'; import Helper from '../../Utilities/Helpers/Helper'; import { getIconStylePresetMappings, isUnsupportedColumnDataTypeForIconStyle, } from '../../Utilities/Helpers/StyledColumns/IconStyleHelper'; import { shouldRenderStyledColumnOnRow } from '../../Utilities/Helpers/StyledColumns/StyledColumnHelper'; import { errorOnce } from '../AdaptableLogger'; const DEFAULT_SIZE = 18; const DEFAULT_GAP = 4; const normaliseIconMappingKey = (key, matchMode) => { if (typeof key === 'string' && matchMode === 'CaseInsensitive') { return key.toLowerCase(); } return key; }; const resolveIconStyleMappingParts = (iconStyle) => { const presetMappings = iconStyle.Preset ? getIconStylePresetMappings(iconStyle.Preset) : []; const userMappings = iconStyle.Mappings ?? []; if (presetMappings.length === 0 || userMappings.length === 0) { return { presetMappings, userMappings }; } const matchMode = iconStyle.MatchMode ?? 'Exact'; const overrideKeys = new Set(userMappings.map((m) => normaliseIconMappingKey(m.Key, matchMode))); const filteredPreset = presetMappings.filter((p) => !overrideKeys.has(normaliseIconMappingKey(p.Key, matchMode))); return { presetMappings: filteredPreset, userMappings }; }; export const resolveEffectiveIconStyleMappings = (iconStyle) => { if (!iconStyle) return []; const { presetMappings, userMappings } = resolveIconStyleMappingParts(iconStyle); if (presetMappings.length === 0) return userMappings; if (userMappings.length === 0) { return iconStyle.Preset ? getIconStylePresetMappings(iconStyle.Preset) : []; } return [...presetMappings, ...userMappings]; }; export const resolveIconStyleMappingsForSummaryPreview = (iconStyle) => { if (!iconStyle) return []; const { presetMappings, userMappings } = resolveIconStyleMappingParts(iconStyle); if (userMappings.length === 0) { if (presetMappings.length > 0) { return presetMappings; } return iconStyle.Preset ? getIconStylePresetMappings(iconStyle.Preset) : []; } if (presetMappings.length === 0) { return userMappings; } return [...userMappings, ...presetMappings]; }; const findMapping = (mappings, cellValue, matchMode) => { if (!mappings || mappings.length === 0) return undefined; if (matchMode === 'CaseInsensitive' && typeof cellValue === 'string') { const needle = cellValue.toLowerCase(); return mappings.find((m) => { if (typeof m.Key === 'string') return m.Key.toLowerCase() === needle; return String(m.Key) === cellValue; }); } return mappings.find((m) => m.Key === cellValue); }; const isInlineGlyph = (spec) => { return typeof spec === 'string'; }; const isAdaptableIconSpec = (spec) => { return (!!spec && (isAdaptableSystemIcon(spec) || isAdaptableCustomIcon(spec) || isAdaptableElementIcon(spec))); }; const renderIconSpec = (spec, size, className) => { if (isInlineGlyph(spec)) { return React.createElement('span', { className, style: { fontSize: size, lineHeight: 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', }, }, spec); } if (isAdaptableIconSpec(spec)) { return React.createElement(IconComponent, { icon: { ...spec, size: spec.size ?? size }, iconClassName: className, }); } return null; }; const renderIconCellTree = (params) => { const { iconSpec, text, textPosition, size, gap } = params; const iconNode = iconSpec ? renderIconSpec(iconSpec, size, 'ab-IconStyle__glyph') : null; const textNode = text ? React.createElement('span', { className: 'ab-IconStyle__text', style: { lineHeight: 1.2 }, }, text) : null; if (!iconNode && !textNode) { return null; } if (!textNode) { return iconNode; } if (!iconNode) { return textNode; } const isStacked = textPosition === 'Above' || textPosition === 'Below'; const wrapperStyle = { display: 'inline-flex', flexDirection: isStacked ? 'column' : 'row', alignItems: 'center', gap, width: '100%', height: '100%', justifyContent: 'center', }; let children; switch (textPosition) { case 'Before': case 'Above': children = [textNode, iconNode]; break; case 'After': case 'Below': default: children = [iconNode, textNode]; break; } return React.createElement('span', { className: 'ab-IconStyle__wrapper', style: wrapperStyle, }, ...children.map((child, idx) => React.cloneElement(child, { key: idx }))); }; export const getIconRendererForColumn = (styledColumn, abColumn, api) => { if (!styledColumn.IconStyle) { return; } if (isUnsupportedColumnDataTypeForIconStyle(abColumn.dataType)) { errorOnce(`[AdapTable] Icon Styled Column (“${styledColumn.Name || styledColumn.ColumnId || 'unnamed'}” on column ${styledColumn.ColumnId}) is not supported when the column data type is ${abColumn.dataType}. Use Badge Style for array columns, or remove IconStyle from this column.`); return undefined; } const iconStyle = styledColumn.IconStyle; const size = iconStyle.Size ?? DEFAULT_SIZE; const gap = iconStyle.Gap ?? DEFAULT_GAP; const cellTextProperties = iconStyle.CellTextProperties; const textPosition = cellTextProperties?.CellTextPosition ?? 'After'; const cellTextTokens = cellTextProperties?.CellText ?? []; const matchMode = iconStyle.MatchMode ?? 'Exact'; const effectiveMappings = resolveEffectiveIconStyleMappings(iconStyle); return class IconCellRenderer { eGui; unmountReactRoot; getAdaptableInstance(params) { return params.context.__adaptable; } init(params) { const adaptable = this.getAdaptableInstance(params); const adaptableApi = adaptable.api; this.eGui = document.createElement('div'); this.eGui.className = 'ab-IconStyle'; this.eGui.style.display = 'inline-flex'; this.eGui.style.alignItems = 'center'; this.eGui.style.height = '100%'; this.eGui.style.width = '100%'; if (!shouldRenderStyledColumnOnRow(styledColumn, params.node, adaptableApi)) { if (params.value != undefined) { this.eGui.append(String(params.value)); } return; } const cellValue = params.value; const cellValueIsEmpty = Helper.objectNotExists(cellValue); const matched = cellValueIsEmpty ? undefined : findMapping(effectiveMappings, cellValue, matchMode); const formatted = params.formatValue?.(cellValue) ?? (cellValue != undefined ? String(cellValue) : ''); let iconSpec; let text; let descriptionForTooltip; if (matched) { iconSpec = matched.Icon; descriptionForTooltip = matched.Description; const textParts = []; if (cellTextTokens.includes('CellValue') && formatted) { textParts.push(formatted); } if (cellTextTokens.includes('IconDescription') && matched.Description) { textParts.push(matched.Description); } text = textParts.length > 0 ? textParts.join(' · ') : undefined; } else if (!cellValueIsEmpty) { const fallbackMode = iconStyle.FallbackProperties?.Mode ?? 'Hide'; const fallbackIcon = iconStyle.FallbackProperties?.Icon; if (fallbackMode === 'ShowText') { text = formatted || undefined; } else if (fallbackMode === 'Icon' && fallbackIcon) { iconSpec = fallbackIcon; } } if (!iconSpec && !text) { return; } const tree = renderIconCellTree({ iconSpec, text, textPosition, size, gap, tooltipExtra: descriptionForTooltip, }); if (!tree) { return; } this.unmountReactRoot = adaptableApi.internalApi .getAdaptableInstance() .renderReactRoot(renderWithAdaptableContext(React.createElement(React.Fragment, { children: tree }), adaptable), this.eGui); } getGui() { return this.eGui; } destroy() { this.unmountReactRoot?.(); } refresh(_params) { return false; } }; }; export const findIconStyleMappingForValue = (iconStyle, cellValue) => { if (!iconStyle) return undefined; const effective = resolveEffectiveIconStyleMappings(iconStyle); return findMapping(effective, cellValue, iconStyle.MatchMode ?? 'Exact'); };