@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
JavaScript
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');
};