From 43900a8b52f225020d55dca97d6af40b54718975 Mon Sep 17 00:00:00 2001 From: iqudoo Date: Mon, 15 Jun 2026 17:53:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E7=BE=8E=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PropSidebar.tsx | 5 +- src/components/TableCellTextFields.tsx | 5 +- src/components/TextDisplayAffixFields.tsx | 2 +- src/components/ValueExtractFields.tsx | 119 ++++++++++----- src/components/VisibilityRuleDialog.tsx | 50 ++++++- src/components/VisibilityRuleForm.tsx | 51 ++++++- src/tableUtils.ts | 2 + src/types.ts | 19 ++- src/utils.ts | 168 ++++++++++++++++------ 9 files changed, 324 insertions(+), 97 deletions(-) diff --git a/src/components/PropSidebar.tsx b/src/components/PropSidebar.tsx index 6c00401..f4c29d9 100644 --- a/src/components/PropSidebar.tsx +++ b/src/components/PropSidebar.tsx @@ -796,7 +796,10 @@ export const PropSidebar: React.FC = ({ /> ) : ( <> - + handleElemChange({ ...selectedElem, ...patch })} + /> {selectedElem.type === 'text' && ( )} diff --git a/src/components/TableCellTextFields.tsx b/src/components/TableCellTextFields.tsx index 3f84ffd..3dabe7b 100644 --- a/src/components/TableCellTextFields.tsx +++ b/src/components/TableCellTextFields.tsx @@ -456,7 +456,10 @@ export const TableCellTextFields: React.FC = ({ - + patchText({ ...textElem, ...patch })} + /> ); diff --git a/src/components/TextDisplayAffixFields.tsx b/src/components/TextDisplayAffixFields.tsx index 205a89b..b46f657 100644 --- a/src/components/TextDisplayAffixFields.tsx +++ b/src/components/TextDisplayAffixFields.tsx @@ -45,7 +45,7 @@ export const TextDisplayAffixFields: React.FC = ({ />

- 在值提取完成后追加,仅影响显示;不参与「内容值」显示条件判断 + 在值提取完成后追加,仅影响显示;反选模式下会一并参与从原值中去除;不参与「内容值」显示条件判断

); diff --git a/src/components/ValueExtractFields.tsx b/src/components/ValueExtractFields.tsx index 53440d5..75e34b4 100644 --- a/src/components/ValueExtractFields.tsx +++ b/src/components/ValueExtractFields.tsx @@ -1,34 +1,39 @@ import React from 'react'; -import { TemplateElement } from '../types'; +import type { ValueExtractConfig } from '../types'; import { CommitInput } from './CommitInput'; import { FieldSelect } from './PsSelect'; interface ValueExtractFieldsProps { - elem: TemplateElement; - onChange: (elem: TemplateElement) => void; + config: ValueExtractConfig; + onChange: (patch: Partial) => void; } const PropInput = CommitInput; -function getEffectiveExtractMode(elem: TemplateElement) { - if (elem.textExtractMode !== undefined) return elem.textExtractMode; - return elem.textSplitDelimiter?.trim() ? 'split' : 'none'; +function getEffectiveExtractMode(config: ValueExtractConfig) { + if (config.textExtractMode !== undefined) return config.textExtractMode; + return config.textSplitDelimiter?.trim() ? 'split' : 'none'; } -export const ValueExtractFields: React.FC = ({ elem, onChange }) => { - const extractMode = getEffectiveExtractMode(elem); +type ExtractSelectValue = 'none' | 'split' | 'prefix' | 'suffix' | 'inverse'; + +export const ValueExtractFields: React.FC = ({ config, onChange }) => { + const isInverse = config.textExtractInverse === true; + const extractMode = getEffectiveExtractMode(config); + const selectValue: ExtractSelectValue = isInverse ? 'inverse' : extractMode; + const showExtractFields = selectValue !== 'none'; return (
{ - const mode = e.target.value as 'none' | 'split' | 'prefix' | 'suffix'; + const mode = e.target.value as ExtractSelectValue; if (mode === 'none') { onChange({ - ...elem, + textExtractInverse: false, textExtractMode: 'none', textSplitDelimiter: undefined, textSplitIndex: undefined, @@ -36,14 +41,23 @@ export const ValueExtractFields: React.FC = ({ elem, on }); return; } + if (mode === 'inverse') { + onChange({ + textExtractInverse: true, + textExtractMode: extractMode === 'none' ? 'prefix' : extractMode, + textSplitIndex: config.textSplitIndex ?? 1, + textExtractLength: config.textExtractLength ?? 1, + }); + return; + } onChange({ - ...elem, + textExtractInverse: false, textExtractMode: mode, - textSplitIndex: mode === 'split' ? elem.textSplitIndex ?? 1 : elem.textSplitIndex, + textSplitIndex: mode === 'split' ? config.textSplitIndex ?? 1 : config.textSplitIndex, textExtractLength: mode === 'prefix' || mode === 'suffix' - ? elem.textExtractLength ?? 1 - : elem.textExtractLength, + ? config.textExtractLength ?? 1 + : config.textExtractLength, }); }} className="ps-field" @@ -52,48 +66,76 @@ export const ValueExtractFields: React.FC = ({ elem, on +
- {extractMode === 'split' && ( + {isInverse && ( +
+ + { + const mode = e.target.value as 'split' | 'prefix' | 'suffix'; + onChange({ + textExtractInverse: true, + textExtractMode: mode, + textSplitIndex: mode === 'split' ? config.textSplitIndex ?? 1 : config.textSplitIndex, + textExtractLength: + mode === 'prefix' || mode === 'suffix' + ? config.textExtractLength ?? 1 + : config.textExtractLength, + }); + }} + className="ps-field" + > + + + + +

+ 从原值中去掉「显示前缀 + 提取段 + 显示后缀」。例如原值 A-1-1-1,提取前 1 位并追加后缀 + 「-」,则得到 1-1-1 +

+
+ )} + {showExtractFields && extractMode === 'split' && (
{ const trimmed = val.trim(); if (!trimmed) { onChange({ - ...elem, - textExtractMode: 'none', + textExtractMode: isInverse ? 'split' : 'none', + textExtractInverse: isInverse ? true : false, textSplitDelimiter: undefined, textSplitIndex: undefined, }); return; } onChange({ - ...elem, textExtractMode: 'split', textSplitDelimiter: trimmed, - textSplitIndex: elem.textSplitIndex ?? 1, + textSplitIndex: config.textSplitIndex ?? 1, }); }} className="ps-field font-mono" />
- + onChange({ - ...elem, textExtractMode: 'split', textSplitIndex: Math.max(1, Math.min(99, Number(val) || 1)), }) @@ -101,34 +143,37 @@ export const ValueExtractFields: React.FC = ({ elem, on className="ps-field font-mono" />
-

- 变量替换为完整内容后,按分隔符拆分并取第 N 段(从 1 开始);段数不足时为空 -

+ {!isInverse && ( +

+ 变量替换为完整内容后,按分隔符拆分并取第 N 段(从 1 开始);段数不足时为空 +

+ )}
)} - {(extractMode === 'prefix' || extractMode === 'suffix') && ( + {showExtractFields && (extractMode === 'prefix' || extractMode === 'suffix') && (
onChange({ - ...elem, textExtractLength: Math.max(1, Math.min(999, Number(val) || 1)), }) } className="ps-field font-mono" /> -

- {extractMode === 'prefix' - ? '变量替换为完整内容后,取最前面的 N 个字符' - : '变量替换为完整内容后,取最后面的 N 个字符'} -

+ {!isInverse && ( +

+ {extractMode === 'prefix' + ? '变量替换为完整内容后,取最前面的 N 个字符' + : '变量替换为完整内容后,取最后面的 N 个字符'} +

+ )}
)}
diff --git a/src/components/VisibilityRuleDialog.tsx b/src/components/VisibilityRuleDialog.tsx index 115ae5c..977f875 100644 --- a/src/components/VisibilityRuleDialog.tsx +++ b/src/components/VisibilityRuleDialog.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { X } from 'lucide-react'; import { VisibilityRule } from '../types'; -import { visibilityOperatorNeedsValue } from '../utils'; +import { visibilityOperatorNeedsValue, valueExtractConfigHasExtract } from '../utils'; import { VisibilityRuleForm, type VisibilityRuleDraft } from './VisibilityRuleForm'; const EMPTY_DRAFT: VisibilityRuleDraft = { @@ -12,6 +12,46 @@ const EMPTY_DRAFT: VisibilityRuleDraft = { value: '', }; +function pickRuleExtractFields(draft: VisibilityRuleDraft): Partial { + if (!valueExtractConfigHasExtract(draft)) { + return { + textExtractMode: undefined, + textSplitDelimiter: undefined, + textSplitIndex: undefined, + textExtractLength: undefined, + textExtractInverse: undefined, + textDisplayPrefix: undefined, + textDisplaySuffix: undefined, + }; + } + + return { + textExtractMode: draft.textExtractMode, + textSplitDelimiter: draft.textSplitDelimiter, + textSplitIndex: draft.textSplitIndex, + textExtractLength: draft.textExtractLength, + textExtractInverse: draft.textExtractInverse || undefined, + textDisplayPrefix: draft.textDisplayPrefix || undefined, + textDisplaySuffix: draft.textDisplaySuffix || undefined, + }; +} + +function ruleToDraft(rule: VisibilityRule): VisibilityRuleDraft { + return { + target: rule.target ?? 'variable', + variable: rule.variable, + operator: rule.operator, + value: rule.value ?? '', + textExtractMode: rule.textExtractMode, + textSplitDelimiter: rule.textSplitDelimiter, + textSplitIndex: rule.textSplitIndex, + textExtractLength: rule.textExtractLength, + textExtractInverse: rule.textExtractInverse, + textDisplayPrefix: rule.textDisplayPrefix, + textDisplaySuffix: rule.textDisplaySuffix, + }; +} + interface VisibilityRuleDialogProps { open: boolean; mode: 'add' | 'edit'; @@ -40,12 +80,7 @@ export const VisibilityRuleDialog: React.FC = ({ useEffect(() => { if (!open) return; if (mode === 'edit' && initialRule) { - setDraft({ - target: initialRule.target ?? 'variable', - variable: initialRule.variable, - operator: initialRule.operator, - value: initialRule.value ?? '', - }); + setDraft(ruleToDraft(initialRule)); } else { setDraft({ ...EMPTY_DRAFT, @@ -83,6 +118,7 @@ export const VisibilityRuleDialog: React.FC = ({ value: visibilityOperatorNeedsValue(draft.operator) ? draft.value?.trim() || undefined : undefined, + ...pickRuleExtractFields(draft), }); onClose(); }; diff --git a/src/components/VisibilityRuleForm.tsx b/src/components/VisibilityRuleForm.tsx index e373a44..68fb9f1 100644 --- a/src/components/VisibilityRuleForm.tsx +++ b/src/components/VisibilityRuleForm.tsx @@ -3,13 +3,16 @@ import { VisibilityRule, VisibilityOperator, VisibilityRuleTarget, + ValueExtractConfig, VISIBILITY_OPERATOR_LABELS, VISIBILITY_RULE_TARGET_LABELS, } from '../types'; -import { visibilityOperatorNeedsValue } from '../utils'; +import { visibilityOperatorNeedsValue, valueExtractConfigHasExtract } from '../utils'; import { FieldSelect } from './PsSelect'; +import { ValueExtractFields } from './ValueExtractFields'; +import { TextDisplayAffixFields } from './TextDisplayAffixFields'; -export interface VisibilityRuleDraft { +export interface VisibilityRuleDraft extends ValueExtractConfig { target: VisibilityRuleTarget; variable: string; operator: VisibilityOperator; @@ -31,6 +34,7 @@ export const VisibilityRuleForm: React.FC = ({ variableOnly = false, }) => { const target = variableOnly ? 'variable' : (rule.target ?? 'variable'); + const hasExtract = valueExtractConfigHasExtract(rule); return (
@@ -81,11 +85,48 @@ export const VisibilityRuleForm: React.FC = ({
) : ( !variableOnly && ( -

- 使用本元素绑定内容解析并提取后的显示文本(含数值格式等;不含前缀/后缀字符) -

+

+ 使用本元素绑定内容的变量替换与数值格式化结果(不含元素自身的值提取与显示前后缀) +

) )} +
+
值提取
+ + {hasExtract && rule.textExtractInverse && ( + + onChange({ + textDisplayPrefix: patch.textDisplayPrefix, + textDisplaySuffix: patch.textDisplaySuffix, + }) + } + /> + )} + {hasExtract && !rule.textExtractInverse && ( +

+ 条件判断前先对{target === 'content' ? '内容值' : '变量值'}做提取,再与目标值比较 +

+ )} +
= { prefix: '前 N 位', suffix: '后 N 位', }; + +/** 变量/内容值提取配置(元素、表格单元格、显示条件等共用) */ +export interface ValueExtractConfig { + textExtractMode?: TextExtractMode; + textSplitDelimiter?: string; + textSplitIndex?: number; + textExtractLength?: number; + /** 反选:从原值中去掉「显示前缀 + 提取段 + 显示后缀」 */ + textExtractInverse?: boolean; + textDisplayPrefix?: string; + textDisplaySuffix?: string; +} + export type ElementVisibilityMode = 'always' | 'conditional'; export type VisibilityOperator = | 'empty' @@ -23,7 +36,7 @@ export type VisibilityOperator = /** 显示条件判断对象:变量值 或 本元素解析后的内容值 */ export type VisibilityRuleTarget = 'variable' | 'content'; -export interface VisibilityRule { +export interface VisibilityRule extends ValueExtractConfig { /** 条件对象,默认 variable */ target?: VisibilityRuleTarget; variable: string; @@ -87,6 +100,8 @@ export interface TableCellData { textSplitDelimiter?: string; textSplitIndex?: number; textExtractLength?: number; + /** 反选:从原值中去掉「显示前缀 + 提取段 + 显示后缀」 */ + textExtractInverse?: boolean; textDisplayPrefix?: string; textDisplaySuffix?: string; } @@ -175,6 +190,8 @@ export interface TemplateElement { textSplitIndex?: number; /** 前 N / 后 N 位提取时的位数 */ textExtractLength?: number; + /** 反选:从原值中去掉「显示前缀 + 提取段 + 显示后缀」 */ + textExtractInverse?: boolean; /** 提取后在显示文本前追加的前缀字符,仅 type=text;不参与内容值条件 */ textDisplayPrefix?: string; /** 提取后在显示文本后追加的后缀字符,仅 type=text;不参与内容值条件 */ diff --git a/src/utils.ts b/src/utils.ts index 8bd75f4..e783826 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,6 +13,7 @@ import { VisibilityRule, VisibilityOperator, VISIBILITY_OPERATOR_LABELS, + ValueExtractConfig, } from './types'; const IMPORT_DATA_STORAGE_PREFIX = 'label_import_data_v2_'; @@ -764,27 +765,29 @@ export function toggleVisibilityRuleEnabled(rule: VisibilityRule): VisibilityRul /** 将显示条件格式化为可读说明 */ export function formatVisibilityRuleDescription(rule: VisibilityRule): string { + const extractNote = formatValueExtractSummary(rule); const subjectLabel = rule.target === 'content' ? '内容值' : `{${rule.variable}}`; + const subject = extractNote ? `${subjectLabel}(${extractNote})` : subjectLabel; switch (rule.operator) { case 'empty': - return `${subjectLabel} 为空`; + return `${subject} 为空`; case 'not_empty': - return `${subjectLabel} 不为空`; + return `${subject} 不为空`; case 'gt': - return `${subjectLabel} 大于 ${rule.value ?? '—'}`; + return `${subject} 大于 ${rule.value ?? '—'}`; case 'lt': - return `${subjectLabel} 小于 ${rule.value ?? '—'}`; + return `${subject} 小于 ${rule.value ?? '—'}`; case 'eq': - return `${subjectLabel} 等于 ${rule.value ?? '—'}`; + return `${subject} 等于 ${rule.value ?? '—'}`; case 'neq': - return `${subjectLabel} 不等于 ${rule.value ?? '—'}`; + return `${subject} 不等于 ${rule.value ?? '—'}`; case 'contains': - return `${subjectLabel} 包含 ${rule.value ?? '—'}`; + return `${subject} 包含 ${rule.value ?? '—'}`; case 'not_contains': - return `${subjectLabel} 不包含 ${rule.value ?? '—'}`; + return `${subject} 不包含 ${rule.value ?? '—'}`; default: - return `${subjectLabel} ${VISIBILITY_OPERATOR_LABELS[rule.operator]}`; + return `${subject} ${VISIBILITY_OPERATOR_LABELS[rule.operator]}`; } } @@ -802,6 +805,13 @@ export function resolveVisibilityRules(elem: TemplateElement): VisibilityRule[] operator: r.operator, value: r.value, enabled: r.enabled, + textExtractMode: r.textExtractMode, + textSplitDelimiter: r.textSplitDelimiter, + textSplitIndex: r.textSplitIndex, + textExtractLength: r.textExtractLength, + textExtractInverse: r.textExtractInverse, + textDisplayPrefix: r.textDisplayPrefix, + textDisplaySuffix: r.textDisplaySuffix, })) .filter((r) => r.target === 'content' || r.variable); } @@ -847,7 +857,7 @@ function compareVariableToTarget( return false; } -/** 读取显示条件所判断的原始值 */ +/** 读取显示条件所判断的原始值(含条件专用值提取) */ function getVisibilityRuleRawValue( rule: VisibilityRule, elem: TemplateElement | undefined, @@ -856,15 +866,29 @@ function getVisibilityRuleRawValue( defaults?: Record | null, mode: ContentResolveMode = 'output' ): string | undefined { + let resolved: string | undefined; + if (isVisibilityContentTarget(rule)) { if (!elem) return undefined; const effectiveRow = row ?? (mode === 'design' && defaults ? (defaults as RecordRow) : null); - const resolved = resolveElementCoreValue(elem, effectiveRow, rowIndex, defaults, 'output'); - const trimmed = resolved.trim(); - return trimmed || undefined; + const formatSubst = textElementNeedsNumberFormatSubst(elem) + ? (raw: string) => formatTextNumberSubstValue(elem, raw) + : undefined; + const trimmed = resolveContent(elem.content, effectiveRow, rowIndex, defaults, { + mode: 'output', + formatSubst, + }).trim(); + resolved = trimmed || undefined; + } else { + resolved = getVariableRawValue(rule.variable, row, defaults, mode); } - return getVariableRawValue(rule.variable, row, defaults, mode); + + if (resolved === undefined) return undefined; + if (!valueExtractConfigHasExtract(rule)) return resolved; + const extracted = applyValueExtractConfig(resolved, rule); + const trimmed = extracted.trim(); + return trimmed || undefined; } /** 解析条件目标值(支持 {变量名}) */ @@ -1183,41 +1207,65 @@ export function applyTextSplitSegment( } /** 解析元素有效的文本提取方式(兼容旧版仅配置分隔符) */ -export function getTextExtractMode(elem: Pick< - TemplateElement, - 'textExtractMode' | 'textSplitDelimiter' | 'textSplitIndex' ->): TextExtractMode { - if (elem.textExtractMode !== undefined) return elem.textExtractMode; - const delimiter = elem.textSplitDelimiter?.trim(); - const splitIndex = elem.textSplitIndex ?? 0; +export function getTextExtractMode( + config: Pick +): TextExtractMode { + if (config.textExtractMode !== undefined) return config.textExtractMode; + const delimiter = config.textSplitDelimiter?.trim(); + const splitIndex = config.textSplitIndex ?? 0; if (delimiter && splitIndex >= 1) return 'split'; return 'none'; } +/** 值提取配置是否生效 */ +export function valueExtractConfigHasExtract(config: ValueExtractConfig): boolean { + const mode = getTextExtractMode(config); + if (mode === 'split') { + const delimiter = config.textSplitDelimiter?.trim(); + const index = config.textSplitIndex ?? 0; + return !!(delimiter && index >= 1); + } + if (mode === 'prefix' || mode === 'suffix') { + return (config.textExtractLength ?? 0) >= 1; + } + return false; +} + +/** 值提取配置的可读摘要 */ +export function formatValueExtractSummary(config: ValueExtractConfig): string | null { + if (!valueExtractConfigHasExtract(config)) return null; + const mode = getTextExtractMode(config); + let base: string; + if (mode === 'split') { + const delimiter = config.textSplitDelimiter?.trim() ?? ''; + const index = config.textSplitIndex ?? 1; + base = `按「${delimiter}」第 ${index} 段`; + } else if (mode === 'prefix') { + base = `前 ${config.textExtractLength ?? 1} 位`; + } else { + base = `后 ${config.textExtractLength ?? 1} 位`; + } + return config.textExtractInverse ? `反选 ${base}` : `提取 ${base}`; +} + /** 按配置提取变量值片段 */ -export function applyTextExtract( - value: string, - elem: Pick< - TemplateElement, - 'textExtractMode' | 'textSplitDelimiter' | 'textSplitIndex' | 'textExtractLength' - > -): string { +export function applyTextExtract(value: string, config: ValueExtractConfig): string { const trimmed = value.trim(); - const mode = getTextExtractMode(elem); + const mode = getTextExtractMode(config); switch (mode) { case 'split': { - const delimiter = elem.textSplitDelimiter?.trim(); - const index = elem.textSplitIndex ?? 0; + const delimiter = config.textSplitDelimiter?.trim(); + const index = config.textSplitIndex ?? 0; if (!delimiter || index < 1) return trimmed; return applyTextSplitSegment(trimmed, delimiter, index); } case 'prefix': { - const n = elem.textExtractLength ?? 0; + const n = config.textExtractLength ?? 0; if (n < 1) return trimmed; return trimmed.slice(0, n); } case 'suffix': { - const n = elem.textExtractLength ?? 0; + const n = config.textExtractLength ?? 0; if (n < 1) return trimmed; return trimmed.slice(-n); } @@ -1226,6 +1274,44 @@ export function applyTextExtract( } } +/** 按配置应用值提取(含反选) */ +export function applyValueExtractConfig(value: string, config: ValueExtractConfig): string { + if (!valueExtractConfigHasExtract(config)) return value; + if (config.textExtractInverse) { + return applyTextExtractInverse(value, { type: 'text', ...config }); + } + return applyTextExtract(value, config); +} + +function removeFirstOccurrence(value: string, needle: string): string { + if (!needle) return value; + const index = value.indexOf(needle); + if (index === -1) return value; + return value.slice(0, index) + value.slice(index + needle.length); +} + +/** 反选:从原值中去掉「显示前缀 + 提取段 + 显示后缀」 */ +export function applyTextExtractInverse( + value: string, + elem: Pick< + ValueExtractConfig, + | 'textExtractMode' + | 'textSplitDelimiter' + | 'textSplitIndex' + | 'textExtractLength' + | 'textDisplayPrefix' + | 'textDisplaySuffix' + > & { type?: TemplateElement['type'] } +): string { + const full = value.trim(); + const extracted = applyTextExtract(full, elem); + const needle = + elem.type === 'text' || elem.textDisplayPrefix || elem.textDisplaySuffix + ? applyTextDisplayAffix(extracted, elem) + : extracted; + return removeFirstOccurrence(full, needle); +} + /** 元素是否启用了变量值提取(文本/条码/二维码/图片内容均有效) */ export function elementHasValueExtract( elem: Pick< @@ -1233,16 +1319,7 @@ export function elementHasValueExtract( 'textExtractMode' | 'textSplitDelimiter' | 'textSplitIndex' | 'textExtractLength' > ): boolean { - const mode = getTextExtractMode(elem); - if (mode === 'split') { - const delimiter = elem.textSplitDelimiter?.trim(); - const index = elem.textSplitIndex ?? 0; - return !!(delimiter && index >= 1); - } - if (mode === 'prefix' || mode === 'suffix') { - return (elem.textExtractLength ?? 0) >= 1; - } - return false; + return valueExtractConfigHasExtract(elem); } /** @deprecated 使用 elementHasValueExtract */ @@ -1358,7 +1435,9 @@ export function resolveElementCoreValue( let resolved = resolveContent(elem.content, row, rowIndex, defaults, { mode, formatSubst }); if (elementHasValueExtract(elem)) { - resolved = applyTextExtract(resolved, elem); + resolved = elem.textExtractInverse + ? applyTextExtractInverse(resolved, elem) + : applyTextExtract(resolved, elem); } return resolved; @@ -1374,6 +1453,7 @@ export function resolveElementValue( ): string { const core = resolveElementCoreValue(elem, row, rowIndex, defaults, mode); if (elem.type !== 'text') return core; + if (elem.textExtractInverse && elementHasValueExtract(elem)) return core; return applyTextDisplayAffix(core, elem); }