@platforma-sdk/model 1.81.0 → 1.81.1

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.
@@ -57,7 +57,7 @@ function evaluateRules(rules, columns) {
57
57
  }]);
58
58
  for (const rule of selectorRules) {
59
59
  const hitIds = baseCollection.filter({ include: rule.match }).getColumnIds();
60
- if (hitIds.length === 0) return result;
60
+ if (hitIds.length === 0) continue;
61
61
  selectorHitsByRule.set(rule, new Set(hitIds.map((id) => (0, _milaboratories_pl_model_common.extractPObjectId)(id))));
62
62
  }
63
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","names":["Annotation","ColumnsCollection","hasColumnData","deriveDistinctLabels"],"sources":["../../../../src/components/PlDataTable/createPlDataTable/utils.ts"],"sourcesContent":["import {\n type AnnotationDataStatus,\n type ColumnUniversalId,\n type PColumn,\n type PColumnSpec,\n type PObjectId,\n Annotation,\n extractPObjectId,\n readAnnotation,\n} from \"@milaboratories/pl-model-common\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n} from \"../../../labels/derive_distinct_labels\";\nimport {\n deriveDistinctTooltips,\n type TooltipEntry,\n} from \"../../../labels/derive_distinct_tooltips\";\nimport type { ColumnOrderRule, ColumnVisibilityRule } from \"./createPlDataTableV3\";\nimport type { ColumnSelector, MatchQualifications } from \"@milaboratories/pl-model-common\";\nimport { ColumnsCollection, hasColumnData, type ColumnRecipe } from \"../../../columns\";\nimport type { GlobalCfgRenderCtx } from \"../../../render/internal\";\nimport { isNil, uniqBy } from \"es-toolkit\";\nimport { getField } from \"@milaboratories/helpers\";\n\n/** Adapt a {@link ColumnRecipe} to the plain {@link RuleColumn} shape. */\nexport function toRuleColumn(col: ColumnRecipe): RuleColumn {\n return { id: extractPObjectId(col.id), spec: col.getSpec() };\n}\n\n/** Check if column should be omitted from the table */\nexport function isColumnHidden(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"hidden\";\n}\n\n/** Check if column is hidden by default */\nexport function isColumnOptional(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"optional\";\n}\n\n/** Column shape consumed by rule evaluation. */\nexport type RuleColumn = Pick<PColumn<PObjectId>, \"id\" | \"spec\">;\n\n/** Get effective visibility for a column. Rule map lookup first, then annotation fallback. */\nexport function getEffectiveVisibility(\n col: RuleColumn,\n visibilityByColId?: Map<PObjectId, ColumnVisibilityRule>,\n): undefined | \"default\" | \"optional\" | \"hidden\" {\n const rule = visibilityByColId?.get(col.id);\n if (rule !== undefined) return rule.visibility;\n if (isColumnHidden(col.spec)) return \"hidden\";\n if (isColumnOptional(col.spec)) return \"optional\";\n return undefined;\n}\n\n/** Get ordering priority for a column. Rule map lookup first, then annotation fallback. */\nexport function getOrderPriority(\n col: RuleColumn,\n orderByColId?: Map<PObjectId, ColumnOrderRule>,\n): undefined | number {\n const rule = orderByColId?.get(col.id);\n if (rule !== undefined) return rule.priority;\n const annotation = Number(readAnnotation(col.spec, Annotation.Table.OrderPriority));\n return isNaN(annotation) ? undefined : annotation;\n}\n\n/**\n * Evaluate display rules against a set of columns and return a map of `colId → winning rule`\n * (first-match-wins, preserving original rule order).\n *\n * Predicate-based rules (`ColumnMatcher`) are evaluated directly on the spec.\n * Selector-based rules (`ColumnSelector`) are matched via the\n * `columnsCollection` service — the inline column ids must be resolvable in\n * the active render ctx for the host to read their specs.\n */\nexport function evaluateRules<R extends { match: ColumnSelector }>(\n rules: R[],\n columns: ColumnRecipe[],\n): Map<PObjectId, R> {\n const result = new Map<PObjectId, R>();\n if (rules.length === 0 || columns.length === 0) return result;\n\n const selectorRules = rules.filter((rule) => typeof rule.match !== \"function\");\n const selectorHitsByRule = new Map<R, Set<PObjectId>>();\n\n if (selectorRules.length > 0) {\n const baseColumns = uniqBy(columns, (col) => col.id);\n const baseCollection = ColumnsCollection([{ columns: baseColumns, isFinal: true }]);\n for (const rule of selectorRules) {\n const hitIds = baseCollection.filter({ include: rule.match }).getColumnIds();\n if (hitIds.length === 0) return result;\n selectorHitsByRule.set(rule, new Set(hitIds.map((id) => extractPObjectId(id))));\n }\n }\n\n for (const col of columns) {\n const colKey = extractPObjectId(col.id);\n for (const rule of rules) {\n const matches = selectorHitsByRule.get(rule)?.has(colKey) ?? false;\n if (matches) {\n result.set(colKey, rule);\n break;\n }\n }\n }\n return result;\n}\n\n/**\n * Build a sidecar map `columnId → AnnotationDataStatus` derived from each\n * recipe's own status. This is *rendering* metadata — kept out of the spec\n * so it cannot perturb column identity (id-bearing spec overrides would\n * diverge the visible PTable's column ids from the full table / pframe).\n *\n * Mapping `ColumnFieldStatus` × actual byte-presence → `AnnotationDataStatus`:\n * - `absent` → `\"absent\"`\n * - `resolving` → `\"computing\"`\n * - `present` + `hasColumnData()` → `\"ready\"`\n * - `present` + no bytes yet → `\"computing\"`\n *\n * Intentionally scoped to visible columns — the probe is meaningful only for\n * what the user will actually see.\n */\nexport function buildDataStatusMap(\n columns: ColumnRecipe[],\n opts: { ctx?: GlobalCfgRenderCtx } = {},\n): Record<ColumnUniversalId, AnnotationDataStatus> {\n return columns.reduce(\n (acc, c) => ((acc[c.id] = deriveDataStatus(c, opts)), acc),\n {} as Record<ColumnUniversalId, AnnotationDataStatus>,\n );\n}\n\nfunction deriveDataStatus(\n column: ColumnRecipe,\n opts: { ctx?: GlobalCfgRenderCtx },\n): AnnotationDataStatus {\n const field = column.getDataStatus();\n if (field === \"absent\") return \"absent\";\n if (field === \"resolving\") return \"computing\";\n return hasColumnData(column, opts) ? \"ready\" : \"computing\";\n}\n\n/** Column shape required by label derivation. */\nexport type LabelableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive labels for all table elements: columns via deriveDistinctLabels, axes from label columns. */\nexport function deriveAllLabels(options: {\n columns: LabelableColumn[];\n deriveLabelsOptions?: DeriveLabelsOptions;\n}): Record<string, string> {\n const { columns, deriveLabelsOptions } = options;\n const entries = columns.map((c) => ({\n spec: c.spec,\n linkerPath: c.linkerPath?.map((step) => ({ spec: step.linker.spec })),\n qualifications: c.qualifications,\n }));\n const columnLabels = deriveDistinctLabels(entries, deriveLabelsOptions).reduce(\n (acc, label, index) => ((acc[columns[index].id] = label), acc),\n {} as Record<string, string>,\n );\n return columnLabels;\n}\n\n/** Column shape required by tooltip derivation. */\nexport type TooltipableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly originalId?: PObjectId;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive origin tooltips for columns whose qualifications or linker path carry info. */\nexport function deriveAllTooltips(options: {\n columns: TooltipableColumn[];\n}): Record<ColumnUniversalId, string> {\n const { columns } = options;\n\n const variantCountByOriginal = columns.reduce<Map<ColumnUniversalId, number>>((acc, c) => {\n return acc.set(getField(c, \"originalId\") ?? c.id, (acc.get(c.originalId ?? c.id) ?? 0) + 1);\n }, new Map());\n\n const { entries } = columns.reduce(\n ({ entries, variantSeen }, c) => {\n const id = getField(c, \"originalId\") ?? c.id;\n const variantCount = variantCountByOriginal.get(id);\n const variantIndex =\n (variantSeen.set(id, (variantSeen.get(id) ?? 0) + 1), variantSeen.get(id));\n\n entries.push({\n spec: c.spec,\n linkerPath: c.linkerPath,\n qualifications: c.qualifications,\n variantIndex,\n variantCount,\n });\n\n return { entries, variantSeen };\n },\n { entries: [] as TooltipEntry[], variantSeen: new Map<ColumnUniversalId, number>() },\n );\n\n const tooltips = deriveDistinctTooltips(entries);\n\n return Object.fromEntries(\n tooltips.flatMap((t, i) => (isNil(t) ? [] : [[columns[i].id, t] as const])),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.cjs","names":["Annotation","ColumnsCollection","hasColumnData","deriveDistinctLabels"],"sources":["../../../../src/components/PlDataTable/createPlDataTable/utils.ts"],"sourcesContent":["import {\n type AnnotationDataStatus,\n type ColumnUniversalId,\n type PColumn,\n type PColumnSpec,\n type PObjectId,\n Annotation,\n extractPObjectId,\n readAnnotation,\n} from \"@milaboratories/pl-model-common\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n} from \"../../../labels/derive_distinct_labels\";\nimport {\n deriveDistinctTooltips,\n type TooltipEntry,\n} from \"../../../labels/derive_distinct_tooltips\";\nimport type { ColumnOrderRule, ColumnVisibilityRule } from \"./createPlDataTableV3\";\nimport type { ColumnSelector, MatchQualifications } from \"@milaboratories/pl-model-common\";\nimport { ColumnsCollection, hasColumnData, type ColumnRecipe } from \"../../../columns\";\nimport type { GlobalCfgRenderCtx } from \"../../../render/internal\";\nimport { isNil, uniqBy } from \"es-toolkit\";\nimport { getField } from \"@milaboratories/helpers\";\n\n/** Adapt a {@link ColumnRecipe} to the plain {@link RuleColumn} shape. */\nexport function toRuleColumn(col: ColumnRecipe): RuleColumn {\n return { id: extractPObjectId(col.id), spec: col.getSpec() };\n}\n\n/** Check if column should be omitted from the table */\nexport function isColumnHidden(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"hidden\";\n}\n\n/** Check if column is hidden by default */\nexport function isColumnOptional(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"optional\";\n}\n\n/** Column shape consumed by rule evaluation. */\nexport type RuleColumn = Pick<PColumn<PObjectId>, \"id\" | \"spec\">;\n\n/** Get effective visibility for a column. Rule map lookup first, then annotation fallback. */\nexport function getEffectiveVisibility(\n col: RuleColumn,\n visibilityByColId?: Map<PObjectId, ColumnVisibilityRule>,\n): undefined | \"default\" | \"optional\" | \"hidden\" {\n const rule = visibilityByColId?.get(col.id);\n if (rule !== undefined) return rule.visibility;\n if (isColumnHidden(col.spec)) return \"hidden\";\n if (isColumnOptional(col.spec)) return \"optional\";\n return undefined;\n}\n\n/** Get ordering priority for a column. Rule map lookup first, then annotation fallback. */\nexport function getOrderPriority(\n col: RuleColumn,\n orderByColId?: Map<PObjectId, ColumnOrderRule>,\n): undefined | number {\n const rule = orderByColId?.get(col.id);\n if (rule !== undefined) return rule.priority;\n const annotation = Number(readAnnotation(col.spec, Annotation.Table.OrderPriority));\n return isNaN(annotation) ? undefined : annotation;\n}\n\n/**\n * Evaluate display rules against a set of columns and return a map of `colId → winning rule`\n * (first-match-wins, preserving original rule order).\n *\n * Predicate-based rules (`ColumnMatcher`) are evaluated directly on the spec.\n * Selector-based rules (`ColumnSelector`) are matched via the\n * `columnsCollection` service — the inline column ids must be resolvable in\n * the active render ctx for the host to read their specs.\n */\nexport function evaluateRules<R extends { match: ColumnSelector }>(\n rules: R[],\n columns: ColumnRecipe[],\n): Map<PObjectId, R> {\n const result = new Map<PObjectId, R>();\n if (rules.length === 0 || columns.length === 0) return result;\n\n const selectorRules = rules.filter((rule) => typeof rule.match !== \"function\");\n const selectorHitsByRule = new Map<R, Set<PObjectId>>();\n\n if (selectorRules.length > 0) {\n const baseColumns = uniqBy(columns, (col) => col.id);\n const baseCollection = ColumnsCollection([{ columns: baseColumns, isFinal: true }]);\n for (const rule of selectorRules) {\n const hitIds = baseCollection.filter({ include: rule.match }).getColumnIds();\n // A rule matching nothing simply contributes no entries — it must not\n // discard the rules evaluated alongside it. The lookup below is\n // null-safe, so leaving this rule out of the map is enough.\n if (hitIds.length === 0) continue;\n selectorHitsByRule.set(rule, new Set(hitIds.map((id) => extractPObjectId(id))));\n }\n }\n\n for (const col of columns) {\n const colKey = extractPObjectId(col.id);\n for (const rule of rules) {\n const matches = selectorHitsByRule.get(rule)?.has(colKey) ?? false;\n if (matches) {\n result.set(colKey, rule);\n break;\n }\n }\n }\n return result;\n}\n\n/**\n * Build a sidecar map `columnId → AnnotationDataStatus` derived from each\n * recipe's own status. This is *rendering* metadata — kept out of the spec\n * so it cannot perturb column identity (id-bearing spec overrides would\n * diverge the visible PTable's column ids from the full table / pframe).\n *\n * Mapping `ColumnFieldStatus` × actual byte-presence → `AnnotationDataStatus`:\n * - `absent` → `\"absent\"`\n * - `resolving` → `\"computing\"`\n * - `present` + `hasColumnData()` → `\"ready\"`\n * - `present` + no bytes yet → `\"computing\"`\n *\n * Intentionally scoped to visible columns — the probe is meaningful only for\n * what the user will actually see.\n */\nexport function buildDataStatusMap(\n columns: ColumnRecipe[],\n opts: { ctx?: GlobalCfgRenderCtx } = {},\n): Record<ColumnUniversalId, AnnotationDataStatus> {\n return columns.reduce(\n (acc, c) => ((acc[c.id] = deriveDataStatus(c, opts)), acc),\n {} as Record<ColumnUniversalId, AnnotationDataStatus>,\n );\n}\n\nfunction deriveDataStatus(\n column: ColumnRecipe,\n opts: { ctx?: GlobalCfgRenderCtx },\n): AnnotationDataStatus {\n const field = column.getDataStatus();\n if (field === \"absent\") return \"absent\";\n if (field === \"resolving\") return \"computing\";\n return hasColumnData(column, opts) ? \"ready\" : \"computing\";\n}\n\n/** Column shape required by label derivation. */\nexport type LabelableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive labels for all table elements: columns via deriveDistinctLabels, axes from label columns. */\nexport function deriveAllLabels(options: {\n columns: LabelableColumn[];\n deriveLabelsOptions?: DeriveLabelsOptions;\n}): Record<string, string> {\n const { columns, deriveLabelsOptions } = options;\n const entries = columns.map((c) => ({\n spec: c.spec,\n linkerPath: c.linkerPath?.map((step) => ({ spec: step.linker.spec })),\n qualifications: c.qualifications,\n }));\n const columnLabels = deriveDistinctLabels(entries, deriveLabelsOptions).reduce(\n (acc, label, index) => ((acc[columns[index].id] = label), acc),\n {} as Record<string, string>,\n );\n return columnLabels;\n}\n\n/** Column shape required by tooltip derivation. */\nexport type TooltipableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly originalId?: PObjectId;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive origin tooltips for columns whose qualifications or linker path carry info. */\nexport function deriveAllTooltips(options: {\n columns: TooltipableColumn[];\n}): Record<ColumnUniversalId, string> {\n const { columns } = options;\n\n const variantCountByOriginal = columns.reduce<Map<ColumnUniversalId, number>>((acc, c) => {\n return acc.set(getField(c, \"originalId\") ?? c.id, (acc.get(c.originalId ?? c.id) ?? 0) + 1);\n }, new Map());\n\n const { entries } = columns.reduce(\n ({ entries, variantSeen }, c) => {\n const id = getField(c, \"originalId\") ?? c.id;\n const variantCount = variantCountByOriginal.get(id);\n const variantIndex =\n (variantSeen.set(id, (variantSeen.get(id) ?? 0) + 1), variantSeen.get(id));\n\n entries.push({\n spec: c.spec,\n linkerPath: c.linkerPath,\n qualifications: c.qualifications,\n variantIndex,\n variantCount,\n });\n\n return { entries, variantSeen };\n },\n { entries: [] as TooltipEntry[], variantSeen: new Map<ColumnUniversalId, number>() },\n );\n\n const tooltips = deriveDistinctTooltips(entries);\n\n return Object.fromEntries(\n tooltips.flatMap((t, i) => (isNil(t) ? [] : [[columns[i].id, t] as const])),\n );\n}\n"],"mappings":"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"}
@@ -57,7 +57,7 @@ function evaluateRules(rules, columns) {
57
57
  }]);
58
58
  for (const rule of selectorRules) {
59
59
  const hitIds = baseCollection.filter({ include: rule.match }).getColumnIds();
60
- if (hitIds.length === 0) return result;
60
+ if (hitIds.length === 0) continue;
61
61
  selectorHitsByRule.set(rule, new Set(hitIds.map((id) => extractPObjectId(id))));
62
62
  }
63
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/components/PlDataTable/createPlDataTable/utils.ts"],"sourcesContent":["import {\n type AnnotationDataStatus,\n type ColumnUniversalId,\n type PColumn,\n type PColumnSpec,\n type PObjectId,\n Annotation,\n extractPObjectId,\n readAnnotation,\n} from \"@milaboratories/pl-model-common\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n} from \"../../../labels/derive_distinct_labels\";\nimport {\n deriveDistinctTooltips,\n type TooltipEntry,\n} from \"../../../labels/derive_distinct_tooltips\";\nimport type { ColumnOrderRule, ColumnVisibilityRule } from \"./createPlDataTableV3\";\nimport type { ColumnSelector, MatchQualifications } from \"@milaboratories/pl-model-common\";\nimport { ColumnsCollection, hasColumnData, type ColumnRecipe } from \"../../../columns\";\nimport type { GlobalCfgRenderCtx } from \"../../../render/internal\";\nimport { isNil, uniqBy } from \"es-toolkit\";\nimport { getField } from \"@milaboratories/helpers\";\n\n/** Adapt a {@link ColumnRecipe} to the plain {@link RuleColumn} shape. */\nexport function toRuleColumn(col: ColumnRecipe): RuleColumn {\n return { id: extractPObjectId(col.id), spec: col.getSpec() };\n}\n\n/** Check if column should be omitted from the table */\nexport function isColumnHidden(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"hidden\";\n}\n\n/** Check if column is hidden by default */\nexport function isColumnOptional(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"optional\";\n}\n\n/** Column shape consumed by rule evaluation. */\nexport type RuleColumn = Pick<PColumn<PObjectId>, \"id\" | \"spec\">;\n\n/** Get effective visibility for a column. Rule map lookup first, then annotation fallback. */\nexport function getEffectiveVisibility(\n col: RuleColumn,\n visibilityByColId?: Map<PObjectId, ColumnVisibilityRule>,\n): undefined | \"default\" | \"optional\" | \"hidden\" {\n const rule = visibilityByColId?.get(col.id);\n if (rule !== undefined) return rule.visibility;\n if (isColumnHidden(col.spec)) return \"hidden\";\n if (isColumnOptional(col.spec)) return \"optional\";\n return undefined;\n}\n\n/** Get ordering priority for a column. Rule map lookup first, then annotation fallback. */\nexport function getOrderPriority(\n col: RuleColumn,\n orderByColId?: Map<PObjectId, ColumnOrderRule>,\n): undefined | number {\n const rule = orderByColId?.get(col.id);\n if (rule !== undefined) return rule.priority;\n const annotation = Number(readAnnotation(col.spec, Annotation.Table.OrderPriority));\n return isNaN(annotation) ? undefined : annotation;\n}\n\n/**\n * Evaluate display rules against a set of columns and return a map of `colId → winning rule`\n * (first-match-wins, preserving original rule order).\n *\n * Predicate-based rules (`ColumnMatcher`) are evaluated directly on the spec.\n * Selector-based rules (`ColumnSelector`) are matched via the\n * `columnsCollection` service — the inline column ids must be resolvable in\n * the active render ctx for the host to read their specs.\n */\nexport function evaluateRules<R extends { match: ColumnSelector }>(\n rules: R[],\n columns: ColumnRecipe[],\n): Map<PObjectId, R> {\n const result = new Map<PObjectId, R>();\n if (rules.length === 0 || columns.length === 0) return result;\n\n const selectorRules = rules.filter((rule) => typeof rule.match !== \"function\");\n const selectorHitsByRule = new Map<R, Set<PObjectId>>();\n\n if (selectorRules.length > 0) {\n const baseColumns = uniqBy(columns, (col) => col.id);\n const baseCollection = ColumnsCollection([{ columns: baseColumns, isFinal: true }]);\n for (const rule of selectorRules) {\n const hitIds = baseCollection.filter({ include: rule.match }).getColumnIds();\n if (hitIds.length === 0) return result;\n selectorHitsByRule.set(rule, new Set(hitIds.map((id) => extractPObjectId(id))));\n }\n }\n\n for (const col of columns) {\n const colKey = extractPObjectId(col.id);\n for (const rule of rules) {\n const matches = selectorHitsByRule.get(rule)?.has(colKey) ?? false;\n if (matches) {\n result.set(colKey, rule);\n break;\n }\n }\n }\n return result;\n}\n\n/**\n * Build a sidecar map `columnId → AnnotationDataStatus` derived from each\n * recipe's own status. This is *rendering* metadata — kept out of the spec\n * so it cannot perturb column identity (id-bearing spec overrides would\n * diverge the visible PTable's column ids from the full table / pframe).\n *\n * Mapping `ColumnFieldStatus` × actual byte-presence → `AnnotationDataStatus`:\n * - `absent` → `\"absent\"`\n * - `resolving` → `\"computing\"`\n * - `present` + `hasColumnData()` → `\"ready\"`\n * - `present` + no bytes yet → `\"computing\"`\n *\n * Intentionally scoped to visible columns — the probe is meaningful only for\n * what the user will actually see.\n */\nexport function buildDataStatusMap(\n columns: ColumnRecipe[],\n opts: { ctx?: GlobalCfgRenderCtx } = {},\n): Record<ColumnUniversalId, AnnotationDataStatus> {\n return columns.reduce(\n (acc, c) => ((acc[c.id] = deriveDataStatus(c, opts)), acc),\n {} as Record<ColumnUniversalId, AnnotationDataStatus>,\n );\n}\n\nfunction deriveDataStatus(\n column: ColumnRecipe,\n opts: { ctx?: GlobalCfgRenderCtx },\n): AnnotationDataStatus {\n const field = column.getDataStatus();\n if (field === \"absent\") return \"absent\";\n if (field === \"resolving\") return \"computing\";\n return hasColumnData(column, opts) ? \"ready\" : \"computing\";\n}\n\n/** Column shape required by label derivation. */\nexport type LabelableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive labels for all table elements: columns via deriveDistinctLabels, axes from label columns. */\nexport function deriveAllLabels(options: {\n columns: LabelableColumn[];\n deriveLabelsOptions?: DeriveLabelsOptions;\n}): Record<string, string> {\n const { columns, deriveLabelsOptions } = options;\n const entries = columns.map((c) => ({\n spec: c.spec,\n linkerPath: c.linkerPath?.map((step) => ({ spec: step.linker.spec })),\n qualifications: c.qualifications,\n }));\n const columnLabels = deriveDistinctLabels(entries, deriveLabelsOptions).reduce(\n (acc, label, index) => ((acc[columns[index].id] = label), acc),\n {} as Record<string, string>,\n );\n return columnLabels;\n}\n\n/** Column shape required by tooltip derivation. */\nexport type TooltipableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly originalId?: PObjectId;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive origin tooltips for columns whose qualifications or linker path carry info. */\nexport function deriveAllTooltips(options: {\n columns: TooltipableColumn[];\n}): Record<ColumnUniversalId, string> {\n const { columns } = options;\n\n const variantCountByOriginal = columns.reduce<Map<ColumnUniversalId, number>>((acc, c) => {\n return acc.set(getField(c, \"originalId\") ?? c.id, (acc.get(c.originalId ?? c.id) ?? 0) + 1);\n }, new Map());\n\n const { entries } = columns.reduce(\n ({ entries, variantSeen }, c) => {\n const id = getField(c, \"originalId\") ?? c.id;\n const variantCount = variantCountByOriginal.get(id);\n const variantIndex =\n (variantSeen.set(id, (variantSeen.get(id) ?? 0) + 1), variantSeen.get(id));\n\n entries.push({\n spec: c.spec,\n linkerPath: c.linkerPath,\n qualifications: c.qualifications,\n variantIndex,\n variantCount,\n });\n\n return { entries, variantSeen };\n },\n { entries: [] as TooltipEntry[], variantSeen: new Map<ColumnUniversalId, number>() },\n );\n\n const tooltips = deriveDistinctTooltips(entries);\n\n return Object.fromEntries(\n tooltips.flatMap((t, i) => (isNil(t) ? [] : [[columns[i].id, t] as const])),\n );\n}\n"],"mappings":";;;;;;;;;;AA0BA,SAAgB,aAAa,KAA+B;CAC1D,OAAO;EAAE,IAAI,iBAAiB,IAAI,EAAE;EAAG,MAAM,IAAI,QAAQ;CAAE;AAC7D;;AAGA,SAAgB,eAAe,MAA6C;CAC1E,OAAO,eAAe,MAAM,WAAW,MAAM,UAAU,MAAM;AAC/D;;AAGA,SAAgB,iBAAiB,MAA6C;CAC5E,OAAO,eAAe,MAAM,WAAW,MAAM,UAAU,MAAM;AAC/D;;AAMA,SAAgB,uBACd,KACA,mBAC+C;CAC/C,MAAM,OAAO,mBAAmB,IAAI,IAAI,EAAE;CAC1C,IAAI,SAAS,KAAA,GAAW,OAAO,KAAK;CACpC,IAAI,eAAe,IAAI,IAAI,GAAG,OAAO;CACrC,IAAI,iBAAiB,IAAI,IAAI,GAAG,OAAO;AAEzC;;AAGA,SAAgB,iBACd,KACA,cACoB;CACpB,MAAM,OAAO,cAAc,IAAI,IAAI,EAAE;CACrC,IAAI,SAAS,KAAA,GAAW,OAAO,KAAK;CACpC,MAAM,aAAa,OAAO,eAAe,IAAI,MAAM,WAAW,MAAM,aAAa,CAAC;CAClF,OAAO,MAAM,UAAU,IAAI,KAAA,IAAY;AACzC;;;;;;;;;;AAWA,SAAgB,cACd,OACA,SACmB;CACnB,MAAM,yBAAS,IAAI,IAAkB;CACrC,IAAI,MAAM,WAAW,KAAK,QAAQ,WAAW,GAAG,OAAO;CAEvD,MAAM,gBAAgB,MAAM,QAAQ,SAAS,OAAO,KAAK,UAAU,UAAU;CAC7E,MAAM,qCAAqB,IAAI,IAAuB;CAEtD,IAAI,cAAc,SAAS,GAAG;EAE5B,MAAM,iBAAiB,kBAAkB,CAAC;GAAE,SADxB,OAAO,UAAU,QAAQ,IAAI,EACc;GAAG,SAAS;EAAK,CAAC,CAAC;EAClF,KAAK,MAAM,QAAQ,eAAe;GAChC,MAAM,SAAS,eAAe,OAAO,EAAE,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa;GAC3E,IAAI,OAAO,WAAW,GAAG,OAAO;GAChC,mBAAmB,IAAI,MAAM,IAAI,IAAI,OAAO,KAAK,OAAO,iBAAiB,EAAE,CAAC,CAAC,CAAC;EAChF;CACF;CAEA,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,SAAS,iBAAiB,IAAI,EAAE;EACtC,KAAK,MAAM,QAAQ,OAEjB,IADgB,mBAAmB,IAAI,IAAI,CAAC,EAAE,IAAI,MAAM,KAAK,OAChD;GACX,OAAO,IAAI,QAAQ,IAAI;GACvB;EACF;CAEJ;CACA,OAAO;AACT;;;;;;;;;;;;;;;;AAiBA,SAAgB,mBACd,SACA,OAAqC,CAAC,GACW;CACjD,OAAO,QAAQ,QACZ,KAAK,OAAQ,IAAI,EAAE,MAAM,iBAAiB,GAAG,IAAI,GAAI,MACtD,CAAC,CACH;AACF;AAEA,SAAS,iBACP,QACA,MACsB;CACtB,MAAM,QAAQ,OAAO,cAAc;CACnC,IAAI,UAAU,UAAU,OAAO;CAC/B,IAAI,UAAU,aAAa,OAAO;CAClC,OAAO,cAAc,QAAQ,IAAI,IAAI,UAAU;AACjD;;AAWA,SAAgB,gBAAgB,SAGL;CACzB,MAAM,EAAE,SAAS,wBAAwB;CAUzC,OAJqB,qBALL,QAAQ,KAAK,OAAO;EAClC,MAAM,EAAE;EACR,YAAY,EAAE,YAAY,KAAK,UAAU,EAAE,MAAM,KAAK,OAAO,KAAK,EAAE;EACpE,gBAAgB,EAAE;CACpB,EACgD,GAAG,mBAAmB,CAAC,CAAC,QACrE,KAAK,OAAO,WAAY,IAAI,QAAQ,MAAM,CAAC,MAAM,OAAQ,MAC1D,CAAC,CAEe;AACpB"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/components/PlDataTable/createPlDataTable/utils.ts"],"sourcesContent":["import {\n type AnnotationDataStatus,\n type ColumnUniversalId,\n type PColumn,\n type PColumnSpec,\n type PObjectId,\n Annotation,\n extractPObjectId,\n readAnnotation,\n} from \"@milaboratories/pl-model-common\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n} from \"../../../labels/derive_distinct_labels\";\nimport {\n deriveDistinctTooltips,\n type TooltipEntry,\n} from \"../../../labels/derive_distinct_tooltips\";\nimport type { ColumnOrderRule, ColumnVisibilityRule } from \"./createPlDataTableV3\";\nimport type { ColumnSelector, MatchQualifications } from \"@milaboratories/pl-model-common\";\nimport { ColumnsCollection, hasColumnData, type ColumnRecipe } from \"../../../columns\";\nimport type { GlobalCfgRenderCtx } from \"../../../render/internal\";\nimport { isNil, uniqBy } from \"es-toolkit\";\nimport { getField } from \"@milaboratories/helpers\";\n\n/** Adapt a {@link ColumnRecipe} to the plain {@link RuleColumn} shape. */\nexport function toRuleColumn(col: ColumnRecipe): RuleColumn {\n return { id: extractPObjectId(col.id), spec: col.getSpec() };\n}\n\n/** Check if column should be omitted from the table */\nexport function isColumnHidden(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"hidden\";\n}\n\n/** Check if column is hidden by default */\nexport function isColumnOptional(spec: { annotations?: Annotation }): boolean {\n return readAnnotation(spec, Annotation.Table.Visibility) === \"optional\";\n}\n\n/** Column shape consumed by rule evaluation. */\nexport type RuleColumn = Pick<PColumn<PObjectId>, \"id\" | \"spec\">;\n\n/** Get effective visibility for a column. Rule map lookup first, then annotation fallback. */\nexport function getEffectiveVisibility(\n col: RuleColumn,\n visibilityByColId?: Map<PObjectId, ColumnVisibilityRule>,\n): undefined | \"default\" | \"optional\" | \"hidden\" {\n const rule = visibilityByColId?.get(col.id);\n if (rule !== undefined) return rule.visibility;\n if (isColumnHidden(col.spec)) return \"hidden\";\n if (isColumnOptional(col.spec)) return \"optional\";\n return undefined;\n}\n\n/** Get ordering priority for a column. Rule map lookup first, then annotation fallback. */\nexport function getOrderPriority(\n col: RuleColumn,\n orderByColId?: Map<PObjectId, ColumnOrderRule>,\n): undefined | number {\n const rule = orderByColId?.get(col.id);\n if (rule !== undefined) return rule.priority;\n const annotation = Number(readAnnotation(col.spec, Annotation.Table.OrderPriority));\n return isNaN(annotation) ? undefined : annotation;\n}\n\n/**\n * Evaluate display rules against a set of columns and return a map of `colId → winning rule`\n * (first-match-wins, preserving original rule order).\n *\n * Predicate-based rules (`ColumnMatcher`) are evaluated directly on the spec.\n * Selector-based rules (`ColumnSelector`) are matched via the\n * `columnsCollection` service — the inline column ids must be resolvable in\n * the active render ctx for the host to read their specs.\n */\nexport function evaluateRules<R extends { match: ColumnSelector }>(\n rules: R[],\n columns: ColumnRecipe[],\n): Map<PObjectId, R> {\n const result = new Map<PObjectId, R>();\n if (rules.length === 0 || columns.length === 0) return result;\n\n const selectorRules = rules.filter((rule) => typeof rule.match !== \"function\");\n const selectorHitsByRule = new Map<R, Set<PObjectId>>();\n\n if (selectorRules.length > 0) {\n const baseColumns = uniqBy(columns, (col) => col.id);\n const baseCollection = ColumnsCollection([{ columns: baseColumns, isFinal: true }]);\n for (const rule of selectorRules) {\n const hitIds = baseCollection.filter({ include: rule.match }).getColumnIds();\n // A rule matching nothing simply contributes no entries — it must not\n // discard the rules evaluated alongside it. The lookup below is\n // null-safe, so leaving this rule out of the map is enough.\n if (hitIds.length === 0) continue;\n selectorHitsByRule.set(rule, new Set(hitIds.map((id) => extractPObjectId(id))));\n }\n }\n\n for (const col of columns) {\n const colKey = extractPObjectId(col.id);\n for (const rule of rules) {\n const matches = selectorHitsByRule.get(rule)?.has(colKey) ?? false;\n if (matches) {\n result.set(colKey, rule);\n break;\n }\n }\n }\n return result;\n}\n\n/**\n * Build a sidecar map `columnId → AnnotationDataStatus` derived from each\n * recipe's own status. This is *rendering* metadata — kept out of the spec\n * so it cannot perturb column identity (id-bearing spec overrides would\n * diverge the visible PTable's column ids from the full table / pframe).\n *\n * Mapping `ColumnFieldStatus` × actual byte-presence → `AnnotationDataStatus`:\n * - `absent` → `\"absent\"`\n * - `resolving` → `\"computing\"`\n * - `present` + `hasColumnData()` → `\"ready\"`\n * - `present` + no bytes yet → `\"computing\"`\n *\n * Intentionally scoped to visible columns — the probe is meaningful only for\n * what the user will actually see.\n */\nexport function buildDataStatusMap(\n columns: ColumnRecipe[],\n opts: { ctx?: GlobalCfgRenderCtx } = {},\n): Record<ColumnUniversalId, AnnotationDataStatus> {\n return columns.reduce(\n (acc, c) => ((acc[c.id] = deriveDataStatus(c, opts)), acc),\n {} as Record<ColumnUniversalId, AnnotationDataStatus>,\n );\n}\n\nfunction deriveDataStatus(\n column: ColumnRecipe,\n opts: { ctx?: GlobalCfgRenderCtx },\n): AnnotationDataStatus {\n const field = column.getDataStatus();\n if (field === \"absent\") return \"absent\";\n if (field === \"resolving\") return \"computing\";\n return hasColumnData(column, opts) ? \"ready\" : \"computing\";\n}\n\n/** Column shape required by label derivation. */\nexport type LabelableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive labels for all table elements: columns via deriveDistinctLabels, axes from label columns. */\nexport function deriveAllLabels(options: {\n columns: LabelableColumn[];\n deriveLabelsOptions?: DeriveLabelsOptions;\n}): Record<string, string> {\n const { columns, deriveLabelsOptions } = options;\n const entries = columns.map((c) => ({\n spec: c.spec,\n linkerPath: c.linkerPath?.map((step) => ({ spec: step.linker.spec })),\n qualifications: c.qualifications,\n }));\n const columnLabels = deriveDistinctLabels(entries, deriveLabelsOptions).reduce(\n (acc, label, index) => ((acc[columns[index].id] = label), acc),\n {} as Record<string, string>,\n );\n return columnLabels;\n}\n\n/** Column shape required by tooltip derivation. */\nexport type TooltipableColumn = {\n readonly id: ColumnUniversalId;\n readonly spec: PColumnSpec;\n readonly originalId?: PObjectId;\n readonly linkerPath?: ReadonlyArray<{ readonly linker: { readonly spec: PColumnSpec } }>;\n readonly qualifications?: MatchQualifications;\n};\n\n/** Derive origin tooltips for columns whose qualifications or linker path carry info. */\nexport function deriveAllTooltips(options: {\n columns: TooltipableColumn[];\n}): Record<ColumnUniversalId, string> {\n const { columns } = options;\n\n const variantCountByOriginal = columns.reduce<Map<ColumnUniversalId, number>>((acc, c) => {\n return acc.set(getField(c, \"originalId\") ?? c.id, (acc.get(c.originalId ?? c.id) ?? 0) + 1);\n }, new Map());\n\n const { entries } = columns.reduce(\n ({ entries, variantSeen }, c) => {\n const id = getField(c, \"originalId\") ?? c.id;\n const variantCount = variantCountByOriginal.get(id);\n const variantIndex =\n (variantSeen.set(id, (variantSeen.get(id) ?? 0) + 1), variantSeen.get(id));\n\n entries.push({\n spec: c.spec,\n linkerPath: c.linkerPath,\n qualifications: c.qualifications,\n variantIndex,\n variantCount,\n });\n\n return { entries, variantSeen };\n },\n { entries: [] as TooltipEntry[], variantSeen: new Map<ColumnUniversalId, number>() },\n );\n\n const tooltips = deriveDistinctTooltips(entries);\n\n return Object.fromEntries(\n tooltips.flatMap((t, i) => (isNil(t) ? [] : [[columns[i].id, t] as const])),\n );\n}\n"],"mappings":";;;;;;;;;;AA0BA,SAAgB,aAAa,KAA+B;CAC1D,OAAO;EAAE,IAAI,iBAAiB,IAAI,EAAE;EAAG,MAAM,IAAI,QAAQ;CAAE;AAC7D;;AAGA,SAAgB,eAAe,MAA6C;CAC1E,OAAO,eAAe,MAAM,WAAW,MAAM,UAAU,MAAM;AAC/D;;AAGA,SAAgB,iBAAiB,MAA6C;CAC5E,OAAO,eAAe,MAAM,WAAW,MAAM,UAAU,MAAM;AAC/D;;AAMA,SAAgB,uBACd,KACA,mBAC+C;CAC/C,MAAM,OAAO,mBAAmB,IAAI,IAAI,EAAE;CAC1C,IAAI,SAAS,KAAA,GAAW,OAAO,KAAK;CACpC,IAAI,eAAe,IAAI,IAAI,GAAG,OAAO;CACrC,IAAI,iBAAiB,IAAI,IAAI,GAAG,OAAO;AAEzC;;AAGA,SAAgB,iBACd,KACA,cACoB;CACpB,MAAM,OAAO,cAAc,IAAI,IAAI,EAAE;CACrC,IAAI,SAAS,KAAA,GAAW,OAAO,KAAK;CACpC,MAAM,aAAa,OAAO,eAAe,IAAI,MAAM,WAAW,MAAM,aAAa,CAAC;CAClF,OAAO,MAAM,UAAU,IAAI,KAAA,IAAY;AACzC;;;;;;;;;;AAWA,SAAgB,cACd,OACA,SACmB;CACnB,MAAM,yBAAS,IAAI,IAAkB;CACrC,IAAI,MAAM,WAAW,KAAK,QAAQ,WAAW,GAAG,OAAO;CAEvD,MAAM,gBAAgB,MAAM,QAAQ,SAAS,OAAO,KAAK,UAAU,UAAU;CAC7E,MAAM,qCAAqB,IAAI,IAAuB;CAEtD,IAAI,cAAc,SAAS,GAAG;EAE5B,MAAM,iBAAiB,kBAAkB,CAAC;GAAE,SADxB,OAAO,UAAU,QAAQ,IAAI,EACc;GAAG,SAAS;EAAK,CAAC,CAAC;EAClF,KAAK,MAAM,QAAQ,eAAe;GAChC,MAAM,SAAS,eAAe,OAAO,EAAE,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa;GAI3E,IAAI,OAAO,WAAW,GAAG;GACzB,mBAAmB,IAAI,MAAM,IAAI,IAAI,OAAO,KAAK,OAAO,iBAAiB,EAAE,CAAC,CAAC,CAAC;EAChF;CACF;CAEA,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,SAAS,iBAAiB,IAAI,EAAE;EACtC,KAAK,MAAM,QAAQ,OAEjB,IADgB,mBAAmB,IAAI,IAAI,CAAC,EAAE,IAAI,MAAM,KAAK,OAChD;GACX,OAAO,IAAI,QAAQ,IAAI;GACvB;EACF;CAEJ;CACA,OAAO;AACT;;;;;;;;;;;;;;;;AAiBA,SAAgB,mBACd,SACA,OAAqC,CAAC,GACW;CACjD,OAAO,QAAQ,QACZ,KAAK,OAAQ,IAAI,EAAE,MAAM,iBAAiB,GAAG,IAAI,GAAI,MACtD,CAAC,CACH;AACF;AAEA,SAAS,iBACP,QACA,MACsB;CACtB,MAAM,QAAQ,OAAO,cAAc;CACnC,IAAI,UAAU,UAAU,OAAO;CAC/B,IAAI,UAAU,aAAa,OAAO;CAClC,OAAO,cAAc,QAAQ,IAAI,IAAI,UAAU;AACjD;;AAWA,SAAgB,gBAAgB,SAGL;CACzB,MAAM,EAAE,SAAS,wBAAwB;CAUzC,OAJqB,qBALL,QAAQ,KAAK,OAAO;EAClC,MAAM,EAAE;EACR,YAAY,EAAE,YAAY,KAAK,UAAU,EAAE,MAAM,KAAK,OAAO,KAAK,EAAE;EACpE,gBAAgB,EAAE;CACpB,EACgD,GAAG,mBAAmB,CAAC,CAAC,QACrE,KAAK,OAAO,WAAY,IAAI,QAAQ,MAAM,CAAC,MAAM,OAAQ,MAC1D,CAAC,CAEe;AACpB"}
package/dist/package.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.81.0";
2
+ var version = "1.81.1";
3
3
  //#endregion
4
4
  Object.defineProperty(exports, "version", {
5
5
  enumerable: true,
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.81.0";
2
+ var version = "1.81.1";
3
3
  //#endregion
4
4
  export { version };
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.81.0",
3
+ "version": "1.81.1",
4
4
  "description": "Platforma.bio SDK / Block Model",
5
5
  "files": [
6
6
  "./dist/**/*",
@@ -33,9 +33,9 @@
33
33
  "zod": "~3.25.76",
34
34
  "@milaboratories/helpers": "1.14.5",
35
35
  "@milaboratories/pl-model-middle-layer": "1.31.0",
36
- "@milaboratories/pl-model-common": "1.47.3",
36
+ "@milaboratories/ptabler-expression-js": "1.2.37",
37
37
  "@milaboratories/pl-error-like": "1.12.10",
38
- "@milaboratories/ptabler-expression-js": "1.2.37"
38
+ "@milaboratories/pl-model-common": "1.47.3"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@vitest/coverage-istanbul": "^4.1.3",
@@ -44,10 +44,10 @@
44
44
  "vitest": "^4.1.3",
45
45
  "@milaboratories/build-configs": "2.0.0",
46
46
  "@milaboratories/pf-driver": "1.9.0",
47
+ "@milaboratories/columns-collection-driver": "0.2.3",
47
48
  "@milaboratories/pf-spec-driver": "1.5.0",
48
49
  "@milaboratories/ts-builder": "1.6.2",
49
- "@milaboratories/ts-configs": "1.4.0",
50
- "@milaboratories/columns-collection-driver": "0.2.3"
50
+ "@milaboratories/ts-configs": "1.4.0"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "ts-builder build --target node",
@@ -207,6 +207,37 @@ describe("evaluateRules", () => {
207
207
  expect(result.get(gid("b"))?.priority).toBe(50);
208
208
  });
209
209
 
210
+ test("a rule matching zero columns does not suppress the other rules", () => {
211
+ const rules: ColumnOrderRule[] = [
212
+ { match: { name: "^nothing-matches-this$" }, priority: 999 },
213
+ { match: { name: "^alpha$" }, priority: 100 },
214
+ { match: { name: "^beta$" }, priority: 50 },
215
+ ];
216
+ const columns = [makeLazyColumn("a", { name: "alpha" }), makeLazyColumn("b", { name: "beta" })];
217
+
218
+ const result = evaluateRules(rules, columns);
219
+
220
+ expect(result.get(gid("a"))?.priority).toBe(100);
221
+ expect(result.get(gid("b"))?.priority).toBe(50);
222
+ expect(result.size).toBe(2);
223
+ });
224
+
225
+ test("rules matching zero columns are skipped regardless of their position", () => {
226
+ const rules: ColumnVisibilityRule[] = [
227
+ { match: { name: "^ghost-a$" }, visibility: "hidden" },
228
+ { match: { name: "^note$" }, visibility: "hidden" },
229
+ { match: { name: "^ghost-b$" }, visibility: "hidden" },
230
+ { match: { name: "^score$" }, visibility: "optional" },
231
+ { match: { name: "^ghost-c$" }, visibility: "hidden" },
232
+ ];
233
+ const columns = [makeLazyColumn("n", { name: "note" }), makeLazyColumn("s", { name: "score" })];
234
+
235
+ const result = evaluateRules(rules, columns);
236
+
237
+ expect(result.get(gid("n"))?.visibility).toBe("hidden");
238
+ expect(result.get(gid("s"))?.visibility).toBe("optional");
239
+ });
240
+
210
241
  test("dedupes columns by id before building spec frame (no duplicate-key crash)", () => {
211
242
  const rules: ColumnVisibilityRule[] = [{ match: { name: "^dup$" }, visibility: "hidden" }];
212
243
  const dup = makeLazyColumn("d", { name: "dup" });
@@ -88,7 +88,10 @@ export function evaluateRules<R extends { match: ColumnSelector }>(
88
88
  const baseCollection = ColumnsCollection([{ columns: baseColumns, isFinal: true }]);
89
89
  for (const rule of selectorRules) {
90
90
  const hitIds = baseCollection.filter({ include: rule.match }).getColumnIds();
91
- if (hitIds.length === 0) return result;
91
+ // A rule matching nothing simply contributes no entries — it must not
92
+ // discard the rules evaluated alongside it. The lookup below is
93
+ // null-safe, so leaving this rule out of the map is enough.
94
+ if (hitIds.length === 0) continue;
92
95
  selectorHitsByRule.set(rule, new Set(hitIds.map((id) => extractPObjectId(id))));
93
96
  }
94
97
  }