@mintplayer/ng-spark 22.24.0 → 22.25.0

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.
Files changed (46) hide show
  1. package/README.md +15 -0
  2. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +5 -3
  3. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  4. package/fesm2022/mintplayer-ng-spark-contributions.mjs +721 -0
  5. package/fesm2022/mintplayer-ng-spark-contributions.mjs.map +1 -0
  6. package/fesm2022/mintplayer-ng-spark-grid.mjs +131 -4
  7. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  8. package/fesm2022/mintplayer-ng-spark-history.mjs +1 -14
  9. package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -1
  10. package/fesm2022/mintplayer-ng-spark-models.mjs +41 -2
  11. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  12. package/fesm2022/mintplayer-ng-spark-moderation.mjs +4 -4
  13. package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -1
  14. package/fesm2022/mintplayer-ng-spark-panels.mjs +9 -1
  15. package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -1
  16. package/fesm2022/mintplayer-ng-spark-pipes.mjs +39 -3
  17. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  18. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +15 -6
  19. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  20. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +924 -16
  21. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  22. package/fesm2022/mintplayer-ng-spark-po-form.mjs +78 -20
  23. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  24. package/fesm2022/mintplayer-ng-spark-query-list.mjs +60 -5
  25. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  26. package/fesm2022/mintplayer-ng-spark-renderers.mjs +28 -1
  27. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  28. package/fesm2022/mintplayer-ng-spark-shell.mjs +31 -13
  29. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  30. package/fesm2022/mintplayer-ng-spark.mjs +4 -1
  31. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  32. package/package.json +7 -2
  33. package/types/mintplayer-ng-spark-client-operations.d.ts +7 -1
  34. package/types/mintplayer-ng-spark-contributions.d.ts +231 -0
  35. package/types/mintplayer-ng-spark-grid.d.ts +35 -3
  36. package/types/mintplayer-ng-spark-history.d.ts +3 -10
  37. package/types/mintplayer-ng-spark-models.d.ts +60 -3
  38. package/types/mintplayer-ng-spark-panels.d.ts +36 -1
  39. package/types/mintplayer-ng-spark-pipes.d.ts +12 -1
  40. package/types/mintplayer-ng-spark-po-detail.d.ts +4 -1
  41. package/types/mintplayer-ng-spark-po-edit.d.ts +305 -1
  42. package/types/mintplayer-ng-spark-po-form.d.ts +64 -15
  43. package/types/mintplayer-ng-spark-query-list.d.ts +26 -1
  44. package/types/mintplayer-ng-spark-renderers.d.ts +44 -2
  45. package/types/mintplayer-ng-spark-shell.d.ts +24 -9
  46. package/types/mintplayer-ng-spark.d.ts +12 -0
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-pipes.mjs","names":[],"sources":["../../pipes/src/translate-key.pipe.ts","../../pipes/src/resolve-translation.pipe.ts","../../pipes/src/input-type.pipe.ts","../../pipes/src/parsed-date.pipe.ts","../../pipes/src/apply-field-template.ts","../../pipes/src/attribute-value.pipe.ts","../../pipes/src/raw-attribute-value.pipe.ts","../../pipes/src/reference-display-value.pipe.ts","../../pipes/src/reference-attr-value.pipe.ts","../../pipes/src/reference-link-route.pipe.ts","../../pipes/src/reference-chips.pipe.ts","../../pipes/src/router-link.pipe.ts","../../pipes/src/as-detail-type.pipe.ts","../../pipes/src/as-detail-columns.pipe.ts","../../pipes/src/as-detail-cell-value.pipe.ts","../../pipes/src/as-detail-display-value.pipe.ts","../../pipes/src/can-create-detail-row.pipe.ts","../../pipes/src/can-delete-detail-row.pipe.ts","../../pipes/src/can-edit-detail-row.pipe.ts","../../pipes/src/lookup-display-type.pipe.ts","../../pipes/src/lookup-display-value.pipe.ts","../../pipes/src/lookup-options.pipe.ts","../../pipes/src/inline-ref-options.pipe.ts","../../pipes/src/error-for-attribute.pipe.ts","../../pipes/src/icon-name.pipe.ts","../../pipes/src/array-value.pipe.ts","../../pipes/src/query-cell.pipe.ts"],"sourcesContent":["import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\n\n@Pipe({ name: 't', pure: false, standalone: true })\nexport class TranslateKeyPipe implements PipeTransform {\n private readonly lang = inject(SparkLanguageService);\n\n transform(key: string): string {\n return this.lang.t(key);\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { TranslatedString, resolveTranslation } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'resolveTranslation', standalone: true, pure: false })\nexport class ResolveTranslationPipe implements PipeTransform {\n transform(value: TranslatedString | undefined, fallback?: string): string {\n return resolveTranslation(value) || fallback || '';\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'inputType', standalone: true, pure: true })\nexport class InputTypePipe implements PipeTransform {\n transform(dataType: string): string {\n switch (dataType) {\n case 'number':\n case 'decimal':\n return 'number';\n case 'boolean':\n return 'checkbox';\n case 'datetime':\n return 'datetime-local';\n case 'date':\n return 'date';\n case 'color':\n return 'color';\n case 'url':\n case 'image':\n // Both hold a URL, so `url` gets the browser's own validation and keyboard. `image` is a\n // link to an image rather than an upload: the value is still a string the author types.\n return 'url';\n default:\n return 'text';\n }\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n/**\n * Parses a wire value into a `Date`, or `null` when it is absent or unparseable.\n *\n * Exists so the detail page and the grid agree. The grid gets its `Date` from `queryCellValue`, which\n * needs a cell and a column and so cannot be reused on a detail attribute; before this, the detail page\n * had no parsing step at all and printed the raw ISO string — `2026-12-31T23:59:00-08:00` in a\n * definition list, while the grid showed `01/01/2027, 08:59` for the same document.\n *\n * The parsing contract is deliberately identical to `queryCellValue`'s: `new Date(...)`, and an\n * unparseable value yields `null` so the caller can fall back to its own text rather than rendering\n * \"Invalid Date\".\n *\n * Note what `new Date(...)` does to an offset, because it is the whole reason this is a separate step:\n * it collapses the value to an instant and forgets the offset it was written with. That is correct for\n * display — a viewer wants the moment in their own zone — but it means a `Date` can never be the thing\n * you reach for when you need the *originating* wall clock. Read that from the wire value instead.\n */\n@Pipe({ name: 'parsedDate', standalone: true, pure: true })\nexport class ParsedDatePipe implements PipeTransform {\n transform(value: unknown): Date | null {\n if (value == null || value === '') return null;\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value !== 'string' && typeof value !== 'number') return null;\n\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? null : parsed;\n }\n}\n","/**\n * Fills a `{Field}` template from a plain object's own fields. Used ONLY for AsDetail\n * nested-object summary display: an embedded value object (no id, no document) is rendered\n * client-side from its own fields. This is deliberately distinct from entity/reference\n * breadcrumbs — those are resolved recursively on the server (BreadcrumbResolver) and the\n * client only reads the resulting strings. Unknown or null placeholders render empty.\n */\nexport function applyFieldTemplate(template: string, data: Record<string, any>): string {\n return template.replace(/\\{(\\w+)\\}/g, (_match, propertyName) => {\n const value = data[propertyName];\n return value != null ? String(value) : '';\n });\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType, LookupReference, PersistentObject, isReservedAsDetailKey, nestedPoToDict, resolveTranslation, resolvedBreadcrumb } from '@mintplayer/ng-spark/models';\nimport { applyFieldTemplate } from './apply-field-template';\n\n@Pipe({ name: 'attributeValue', standalone: true, pure: true })\nexport class AttributeValuePipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null, entityType: EntityType | null, lookupRefOptions: Record<string, LookupReference>, allEntityTypes: EntityType[]): any {\n if (!item) return '';\n const attr = item.attributes.find(a => a.name === attrName);\n if (!attr) return '';\n\n if (attr.breadcrumb) return attr.breadcrumb;\n\n const attrDef = entityType?.attributes.find(a => a.name === attrName);\n if (attrDef?.dataType === 'AsDetail') {\n // Server emits nested PO(s) in attr.objects (array) / attr.object (single) — attr.value is null.\n if (attr.isArray) {\n const count = attr.objects?.length ?? 0;\n if (count === 0) return '';\n return `${count} item${count !== 1 ? 's' : ''}`;\n }\n if (attr.object) {\n // The server has already resolved this object's breadcrumb, and it can resolve templates\n // this side cannot. A type's breadcrumb may name a computed property that `[IgnoreProperty]`\n // keeps out of the model — HR's `Address.Crumb` is exactly that, `[Breadcrumb, IgnoreProperty]`\n // — so the template `{Crumb}` has no matching attribute here and never will. Recomputing it\n // client-side produced an empty string and fell through to \"(object)\" while the correct\n // \"Voorbeeldstraat 1, 1000 Brussel\" sat unread on `attr.object.breadcrumb`.\n //\n // One value on that property is NOT a breadcrumb: when the template renders blank the\n // server substitutes the CLR type name, and printing it reads as data — a `Build` whose\n // `Feedback.State` was unset showed the literal \"BuildFeedback\" (#384). Filtering it here\n // lets the fallbacks below run, which is what they are for.\n const resolved = resolvedBreadcrumb(attr.object.breadcrumb, attrDef.asDetailType);\n if (resolved) return resolved;\n return this.formatAsDetailValue(attrDef, nestedPoToDict(attr.object), allEntityTypes);\n }\n }\n\n if (attrDef?.lookupReferenceType && attr.value != null && attr.value !== '') {\n const lookupRef = lookupRefOptions[attrDef.lookupReferenceType];\n if (lookupRef) {\n const option = lookupRef.values.find(v => v.key === String(attr.value));\n if (option) {\n return resolveTranslation(option.values) || option.key;\n }\n }\n }\n\n if (attrDef?.dataType === 'boolean') {\n return attr.value ?? null;\n }\n\n return attr.value ?? '';\n }\n\n private formatAsDetailValue(attrDef: EntityAttributeDefinition, value: Record<string, any>, allEntityTypes: EntityType[]): string {\n const asDetailType = allEntityTypes.find(t => t.clrType === attrDef.asDetailType);\n\n if (asDetailType?.breadcrumb) {\n const result = applyFieldTemplate(asDetailType.breadcrumb, value);\n if (result && result.trim()) return result;\n }\n\n // Last resort, reached when the type declares no breadcrumb or its template resolved to\n // nothing. Joining the scalar values is always more use to a reader than \"(object)\", which\n // named the failure rather than the row and looked identical for every unresolvable cell.\n // Reserved keys are skipped: `nestedPoToDict` stashes the row key and the row's own breadcrumb\n // in this same dict, and joining those prints a guid and a placeholder as though they were\n // fields. Previously masked -- this branch was only reachable when the server sent no\n // breadcrumb, which is exactly when there was nothing stashed to leak.\n return Object.entries(value)\n .filter(([k, v]) => !isReservedAsDetailKey(k) && v != null && typeof v !== 'object' && String(v).trim() !== '')\n .map(([, v]) => v)\n .join(', ');\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { PersistentObject } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'rawAttributeValue', standalone: true, pure: true })\nexport class RawAttributeValuePipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null): any {\n return item?.attributes.find(a => a.name === attrName)?.value;\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { EntityAttributeDefinition, PersistentObject, QueryResultItem } from '@mintplayer/ng-spark/models';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\n\n@Pipe({ name: 'referenceDisplayValue', standalone: true, pure: true })\nexport class ReferenceDisplayValuePipe implements PipeTransform {\n private readonly lang = inject(SparkLanguageService);\n\n transform(attr: EntityAttributeDefinition, formData: Record<string, any>, referenceOptions: Record<string, QueryResultItem[]>): string {\n const selectedId = formData[attr.name];\n if (!selectedId) return this.lang.t('common.notSelected');\n\n const options = referenceOptions[attr.name] || [];\n const selected = options.find(o => o.id === selectedId);\n // The server resolves a row's display string into `breadcrumb`; falling back to the id\n // beats showing nothing, and there is no third source now that a row carries no `name`.\n return selected?.breadcrumb || selectedId;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { SparkRow, valueFor } from '@mintplayer/ng-spark/models';\n\n/**\n * The text a reference picker's result grid shows in one column.\n *\n * ⚠️ **Reads the row through {@link valueFor}, not through a shape it assumes.** This used to do\n * `item.attributes.find(...)`, which is the `PersistentObject` shape — but a picker's rows are\n * `QueryResultItem`s, which carry `values` (`[{ key, value, breadcrumb }]`) and have no `attributes`\n * at all. Every cell therefore threw `can't access property \"find\", item.attributes is undefined`,\n * and the picker rendered nothing selectable.\n *\n * There are three row shapes in this codebase and `valueFor` exists precisely so a consumer does not\n * have to know which one it was handed. Using it is also what keeps this working if a picker is ever\n * pointed at an AsDetail sub-table, whose rows are the flat record shape.\n */\n@Pipe({ name: 'referenceAttrValue', standalone: true, pure: true })\nexport class ReferenceAttrValuePipe implements PipeTransform {\n transform(item: SparkRow, attrName: string): any {\n const cell = valueFor(item, attrName);\n if (!cell) return '';\n // Breadcrumb first: a reference cell's value is an id, and the id is not what a person reads.\n if (cell.breadcrumb) return cell.breadcrumb;\n return cell.value ?? '';\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityType } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'referenceLinkRoute', standalone: true, pure: true })\nexport class ReferenceLinkRoutePipe implements PipeTransform {\n transform(referenceClrType: string, referenceId: any, allEntityTypes: EntityType[]): string[] | null {\n if (!referenceId || !referenceClrType) return null;\n const targetType = allEntityTypes.find(t => t.clrType === referenceClrType);\n if (!targetType) return null;\n\n // Presence in the catalogue means the caller may LIST this type; opening one is a different\n // right. Without this a type granted Query but not Read rendered a clickable reference whose\n // click refused on arrival — while the grid's own first column, three components away, gated on\n // canRead and got it right. Same question, two answers.\n if (targetType.canRead === false) return null;\n\n return ['/po', targetType.alias || targetType.id, referenceId];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { PersistentObject } from '@mintplayer/ng-spark/models';\n\nexport interface ReferenceChip {\n id: string;\n label: string;\n}\n\n/**\n * Resolves a multi-reference attribute (`dataType === 'Reference'`, `isArray === true`)\n * to a list of chips. The attribute's `value` carries the id array; `breadcrumbs` (a\n * server-resolved id → label map) provides each chip's display label, falling back to\n * the id when no breadcrumb is present. Mirrors the single-reference `breadcrumb`\n * display, applied per id.\n */\n@Pipe({ name: 'referenceChips', standalone: true, pure: true })\nexport class ReferenceChipsPipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null): ReferenceChip[] {\n const attr = item?.attributes.find(a => a.name === attrName);\n if (!attr || !Array.isArray(attr.value)) return [];\n const breadcrumbs = attr.breadcrumbs ?? {};\n return attr.value\n .filter(v => v != null && v !== '')\n .map(v => {\n const id = String(v);\n return { id, label: breadcrumbs[id] || id };\n });\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { ProgramUnit } from '@mintplayer/ng-spark/models';\n\n/**\n * Maps a program unit to the router commands that open it. Type comparisons are exact: the\n * server's loader canonicalizes `type` casing, so tolerating variants here would only mask a\n * server that stopped doing so.\n *\n * Returns null for a `url` unit — an external address is an `<a href>`, not a router link; the\n * component rendering the menu owns that branch.\n */\n@Pipe({ name: 'routerLink', standalone: true, pure: true })\nexport class RouterLinkPipe implements PipeTransform {\n transform(unit: ProgramUnit): string[] | null {\n if (unit.type === 'query') {\n return ['/query', unit.alias || unit.queryId!];\n } else if (unit.type === 'persistentObject') {\n const type = unit.alias || unit.persistentObjectId!;\n return unit.objectId ? ['/po', type, unit.objectId] : ['/po', type];\n } else if (unit.type === 'url') {\n return null;\n }\n return ['/'];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'asDetailType', standalone: true, pure: true })\nexport class AsDetailTypePipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, asDetailTypes: Record<string, EntityType>): EntityType | null {\n return asDetailTypes[attr.name] || null;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'asDetailColumns', standalone: true, pure: true })\nexport class AsDetailColumnsPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, asDetailTypes: Record<string, EntityType>): EntityAttributeDefinition[] {\n const type = asDetailTypes[attr.name];\n if (!type) return [];\n return type.attributes\n .filter(a => a.isVisible)\n .sort((a, b) => a.order - b.order);\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { AS_DETAIL_BREADCRUMBS_KEY, EntityAttributeDefinition, PersistentObject, QueryResultItem, selfBreadcrumb } from '@mintplayer/ng-spark/models';\n\n/** What a cell can resolve to: the column's own type, or `null` when it has no value. */\nexport type AsDetailCellValue = string | number | boolean | Date | null;\n\n/**\n * The value of one cell in an `AsDetail` table, **typed by the column's `dataType`** rather than\n * flattened to a string.\n *\n * <p>\n * This used to end in `return String(value)` for everything, and declare `: string`. That is fine\n * for a cell which is only interpolated, and wrong for one whose consumer inspects it —\n * `spark-grid-cell` branches on the column type and binds `[checked]=\"display() === true\"` and\n * `[indeterminate]=\"display() == null\"` for a boolean, so it needs the value and not a rendering\n * of it.\n * </p>\n * <p>\n * The visible symptom was a detail page showing unticked checkboxes for rows whose edit form showed\n * them ticked — the edit form reads `attribute.objects` directly, so it never went through here. All\n * three states of a `bool?` were affected, and one of them silently:\n * </p>\n * <ul>\n * <li><b>true</b> — `'true' === true` is false, so it rendered <b>unchecked</b>;</li>\n * <li><b>null</b> — became `''`, and `'' == null` is false, so it rendered unchecked <b>and\n * determinate</b>, i.e. claiming to be false;</li>\n * <li><b>false</b> — `'false'` is not `=== true`, so it rendered correctly <b>by accident</b>.</li>\n * </ul>\n * <p>\n * Types that stay strings do so deliberately, not by omission. `color`, `image` and `url` are bound\n * into a style, an `src` and an `href`, so a string is what they need. `Reference` and `AsDetail`\n * resolve to a breadcrumb, which is a label rather than a value.\n * </p>\n * <p>\n * `date` and `datetime` return a real `Date`. They arrive as ISO strings, and the cell used to\n * print that string verbatim — `2026-09-07T13:25:57.3119825Z` in a table column. Returning the\n * parsed value lets `spark-grid-cell` format it in one place for both grids; an unparseable value\n * falls back to its text, because showing what is there beats showing `Invalid Date`.\n * </p>\n */\n@Pipe({ name: 'asDetailCellValue', standalone: true, pure: true })\nexport class AsDetailCellValuePipe implements PipeTransform {\n transform(\n row: Record<string, any>,\n parentAttr: EntityAttributeDefinition,\n col: EntityAttributeDefinition,\n asDetailRefOptions: Record<string, Record<string, QueryResultItem[]>>,\n ): AsDetailCellValue {\n const value = row[col.name];\n\n // Before the null short-circuit, because for these types \"absent\" is a value the consumer has\n // to be able to see: `null` is what makes a checkbox indeterminate rather than false, and it is\n // what distinguishes \"no number\" from the number zero. `''` erases both.\n switch (col.dataType) {\n case 'boolean':\n return typeof value === 'boolean' ? value : null;\n\n case 'number':\n case 'decimal': {\n if (value == null || value === '') return null;\n const asNumber = typeof value === 'number' ? value : Number(value);\n // A value that is not a number is passed through as text rather than reported as NaN: the\n // cell's job is to show what is there, and \"NaN\" tells the reader nothing about it.\n return Number.isNaN(asNumber) ? String(value) : asNumber;\n }\n\n case 'date':\n case 'datetime': {\n if (value == null || value === '') return null;\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n const parsed = new Date(value as string);\n // Same rule as the number branch: an unparseable date shows its own text rather than\n // \"Invalid Date\", which would hide the value that is actually stored.\n return Number.isNaN(parsed.getTime()) ? String(value) : parsed;\n }\n }\n\n if (value == null) return '';\n\n if (col.dataType === 'Reference' && col.query) {\n // Prefer the breadcrumb the server already resolved by id — it is page-independent, so it\n // renders the label even when the referenced document falls outside the reference query's\n // first options page (issue #185).\n const serverBreadcrumb = (row[AS_DETAIL_BREADCRUMBS_KEY] as Record<string, string> | undefined)?.[col.name];\n if (serverBreadcrumb) return serverBreadcrumb;\n\n // Fallback: resolve against the loaded options page (legacy path).\n const parentOptions = asDetailRefOptions[parentAttr.name];\n if (parentOptions) {\n const options = parentOptions[col.name];\n if (options) {\n const match = options.find(o => o.id === value);\n if (match) return match.breadcrumb || String(value);\n }\n }\n }\n\n // A nested-AsDetail column's value is an inner dict, so String(value) produced \"[object\n // Object]\". The flattener kept that object's server-resolved breadcrumb for exactly this.\n if (col.dataType === 'AsDetail' && typeof value === 'object') {\n return selfBreadcrumb(value as Record<string, any>, col.asDetailType) ?? '';\n }\n\n return String(value);\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType, selfBreadcrumb } from '@mintplayer/ng-spark/models';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport { applyFieldTemplate } from './apply-field-template';\n\n@Pipe({ name: 'asDetailDisplayValue', standalone: true, pure: true })\nexport class AsDetailDisplayValuePipe implements PipeTransform {\n private readonly lang = inject(SparkLanguageService);\n\n transform(attr: EntityAttributeDefinition, formData: Record<string, any>, asDetailTypes: Record<string, EntityType>): string {\n const value = formData[attr.name];\n if (!value) return this.lang.t('common.notSet');\n\n const asDetailType = asDetailTypes[attr.name] || null;\n\n // The server's own resolution wins, because it can render templates this side cannot: a\n // breadcrumb may name a property that `[IgnoreProperty]` deliberately keeps out of the model\n // (HR's `Address.Crumb`), and no amount of client-side substitution will ever find it. Passing\n // the type name filters out the mapper's \"template rendered blank\" placeholder, which is the\n // bare type name rather than an empty string — see `selfBreadcrumb`.\n const resolved = selfBreadcrumb(value, asDetailType?.name);\n if (resolved) return resolved;\n\n // Still correct for a template naming real, projected attributes (`\"{Street}, {City}\"`), which\n // is the common case and the only one reachable on the create path, where there is no server\n // object to have resolved anything yet.\n if (asDetailType?.breadcrumb) {\n const result = applyFieldTemplate(asDetailType.breadcrumb, value);\n if (result && result.trim()) return result;\n }\n\n return this.lang.t('common.clickToEdit');\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityPermissions } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'canCreateDetailRow', standalone: true, pure: true })\nexport class CanCreateDetailRowPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, permissions: Record<string, EntityPermissions>): boolean {\n const perms = permissions[attr.name];\n // Fail closed. An absent entry means the child type's permissions were never fetched, not that\n // the user may do this -- and since the save path now enforces `New/{RowType}`, defaulting\n // to true renders a button whose save is refused. The fetch is also not guaranteed: it used to\n // go through the entity-type catalogue, which is Query-gated, so a user with `New` but not\n // `Query` never got an entry at all.\n return perms ? perms.canCreate : false;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityPermissions } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'canDeleteDetailRow', standalone: true, pure: true })\nexport class CanDeleteDetailRowPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, permissions: Record<string, EntityPermissions>): boolean {\n const perms = permissions[attr.name];\n // Fail closed. An absent entry means the child type's permissions were never fetched, not that\n // the user may do this -- and since the save path now enforces `Delete/{RowType}`, defaulting\n // to true renders a button whose save is refused. The fetch is also not guaranteed: it used to\n // go through the entity-type catalogue, which is Query-gated, so a user with `Delete` but not\n // `Query` never got an entry at all.\n return perms ? perms.canDelete : false;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityPermissions } from '@mintplayer/ng-spark/models';\n\n/**\n * Whether the caller may edit rows of an AsDetail attribute's row type.\n *\n * The third of the `can*DetailRow` family, and the one the server was already answering. `canEdit`\n * has been computed, shipped and fetched per AsDetail attribute since row rights landed — it was\n * simply never read for the row path, so the form offered an edit the save would undo.\n *\n * ⚠️ Apply this **read-only, never hidden**. A row the caller may not edit must stay fully\n * readable; hiding the inputs would hide the data, which is a worse answer than showing it\n * uneditable.\n */\n@Pipe({ name: 'canEditDetailRow', standalone: true, pure: true })\nexport class CanEditDetailRowPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, permissions: Record<string, EntityPermissions>): boolean {\n const perms = permissions[attr.name];\n // Fail closed, for the same reason as its two siblings: an absent entry means the row type's\n // permissions were never fetched, not that the user may edit. And the save path enforces\n // `Edit/{RowType}` independently -- it silently restores the stored row and warns -- so\n // defaulting to true offers an edit whose result is discarded.\n return perms ? perms.canEdit : false;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { ELookupDisplayType, EntityAttributeDefinition, LookupReference } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'lookupDisplayType', standalone: true, pure: true })\nexport class LookupDisplayTypePipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, lookupRefOptions: Record<string, LookupReference>): ELookupDisplayType {\n const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;\n return lookupRef?.displayType ?? ELookupDisplayType.Dropdown;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, LookupReference, resolveTranslation } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'lookupDisplayValue', standalone: true, pure: true })\nexport class LookupDisplayValuePipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, formData: Record<string, any>, lookupRefOptions: Record<string, LookupReference>): string {\n const currentValue = formData[attr.name];\n if (currentValue == null || currentValue === '') return '';\n\n const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;\n const options = lookupRef?.values.filter(v => v.isActive) || [];\n const selected = options.find(o => o.key === String(currentValue));\n if (!selected) return String(currentValue);\n\n return resolveTranslation(selected.values) || selected.key;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, LookupReference, LookupReferenceValue } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'lookupOptions', standalone: true, pure: true })\nexport class LookupOptionsPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, lookupRefOptions: Record<string, LookupReference>): LookupReferenceValue[] {\n const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;\n return lookupRef?.values.filter(v => v.isActive) || [];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, PersistentObject, QueryResultItem } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'inlineRefOptions', standalone: true, pure: true })\nexport class InlineRefOptionsPipe implements PipeTransform {\n transform(parentAttr: EntityAttributeDefinition, col: EntityAttributeDefinition, asDetailRefOptions: Record<string, Record<string, QueryResultItem[]>>): QueryResultItem[] {\n return asDetailRefOptions[parentAttr.name]?.[col.name] || [];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { ValidationError, resolveTranslation } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'errorForAttribute', standalone: true, pure: true })\nexport class ErrorForAttributePipe implements PipeTransform {\n transform(attrName: string, validationErrors: ValidationError[]): string | null {\n const error = validationErrors.find(e => e.attributeName === attrName);\n return error ? resolveTranslation(error.errorMessage) : null;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'iconName', standalone: true, pure: true })\nexport class IconNamePipe implements PipeTransform {\n transform(value: string | undefined, fallback: string): string {\n const iconClass = value || fallback;\n // Strip 'bi-' prefix if present\n return iconClass.startsWith('bi-') ? iconClass.substring(3) : iconClass;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { PersistentObject, nestedPoToDisplayRow } from '@mintplayer/ng-spark/models';\n\n/**\n * Resolves an attribute to a list of flat row dicts for the detail-page table.\n * AsDetail array attributes carry their data as nested PersistentObjects in\n * <c>attr.objects</c>, not <c>attr.value</c> — the server stopped putting flat\n * dicts in <c>value</c> when AsDetail moved to its dedicated wire shape. Without\n * this branch, the detail page rendered AsDetail arrays as empty tables even\n * when the edit page (which reads <c>attr.objects</c> directly) showed rows.\n */\n@Pipe({ name: 'arrayValue', standalone: true, pure: true })\nexport class ArrayValuePipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null): Record<string, any>[] {\n const attr = item?.attributes.find(a => a.name === attrName);\n if (!attr) return [];\n if (attr.dataType === 'AsDetail' && attr.isArray && Array.isArray(attr.objects)) {\n return attr.objects.map(po => nestedPoToDisplayRow(po));\n }\n if (Array.isArray(attr.value)) return attr.value;\n return [];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport {\n LookupReference,\n QueryColumn,\n QueryResultItem,\n resolveTranslation,\n resolvedBreadcrumb,\n valueFor,\n} from '@mintplayer/ng-spark/models';\nimport { ReferenceChip } from './reference-chips.pipe';\n\n/**\n * The display text of one query-grid cell.\n *\n * The attribute-shaped sibling of this ({@link AttributeValuePipe}) still serves detail and edit\n * pages, which do work in attributes. They are deliberately not one pipe: a query row is a flat\n * projection with no nested objects and no per-row metadata, so the fallbacks that make sense\n * there — reaching into `attr.object`, recomputing a breadcrumb template client-side — are not\n * merely unnecessary here, they are unreachable. Sharing the code would mean carrying branches\n * that can never fire and inviting the next reader to feed one shape into the other.\n */\n@Pipe({ name: 'queryCellValue', standalone: true, pure: true })\nexport class QueryCellValuePipe implements PipeTransform {\n transform(\n column: QueryColumn,\n item: QueryResultItem | null,\n lookupRefOptions: Record<string, LookupReference>,\n ): any {\n const cell = valueFor(item, column.name);\n if (!cell) return '';\n\n // A resolved reference display wins: the server can resolve breadcrumb templates this side\n // cannot (a template may name a computed property `[IgnoreProperty]` keeps out of the model).\n // Except the CLR type name, which is the server's placeholder for a template that rendered\n // blank, not a label (#384). `asDetailType` is undefined on every other kind of column, so\n // nothing else is filtered.\n const resolved = resolvedBreadcrumb(cell.breadcrumb, column.asDetailType);\n if (resolved) return resolved;\n\n if (column.dataType === 'AsDetail') {\n // An array cell carries a child COUNT rather than the children, so the wording — and its\n // pluralisation — is decided here, where the language is. A single-child cell carries the\n // child object (for a renderer to read) and is displayed by the server-resolved breadcrumb\n // above, never by stringifying it here.\n if (!column.isArray) return '';\n const count = typeof cell.value === 'number' ? cell.value : 0;\n return count === 0 ? '' : `${count} item${count !== 1 ? 's' : ''}`;\n }\n\n if (column.lookupReferenceType && cell.value != null && cell.value !== '') {\n const lookupRef = lookupRefOptions[column.lookupReferenceType];\n const option = lookupRef?.values.find(v => v.key === String(cell.value));\n if (option) return resolveTranslation(option.values) || option.key;\n }\n\n // Null rather than '' so the cell can tell \"false\" from \"unset\" and render an indeterminate\n // checkbox instead of an unchecked one.\n if (column.dataType === 'boolean') return cell.value ?? null;\n\n // Dates are parsed here for the same reason booleans are passed through above: the cell needs\n // the value, not a rendering of it. Both grids feed `spark-grid-cell`, so both have to agree on\n // the type or the same `datetime` column formats one way in a query grid and another in an\n // AsDetail table. Unparseable input keeps its own text rather than becoming \"Invalid Date\".\n if (column.dataType === 'date' || column.dataType === 'datetime') {\n if (cell.value == null || cell.value === '') return null;\n if (cell.value instanceof Date) return Number.isNaN(cell.value.getTime()) ? null : cell.value;\n const parsed = new Date(cell.value as string);\n return Number.isNaN(parsed.getTime()) ? String(cell.value) : parsed;\n }\n\n return cell.value ?? '';\n }\n}\n\n/**\n * Resolves a multi-reference column (`dataType === 'Reference'`, `isArray === true`) to chips.\n *\n * The cell's `value` carries the id array and `breadcrumbs` the server-resolved id → label map,\n * falling back to the id itself when a label is missing — the same display rule as a single\n * reference, applied per id.\n */\n@Pipe({ name: 'queryReferenceChips', standalone: true, pure: true })\nexport class QueryReferenceChipsPipe implements PipeTransform {\n transform(column: QueryColumn, item: QueryResultItem | null): ReferenceChip[] {\n const cell = valueFor(item, column.name);\n if (!cell || !Array.isArray(cell.value)) return [];\n\n const breadcrumbs = cell.breadcrumbs ?? {};\n return cell.value\n .filter(v => v != null && v !== '')\n .map(v => {\n const id = String(v);\n return { id, label: breadcrumbs[id] || id };\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mintplayer-ng-spark-pipes.mjs","names":[],"sources":["../../pipes/src/translate-key.pipe.ts","../../pipes/src/resolve-translation.pipe.ts","../../pipes/src/input-type.pipe.ts","../../pipes/src/parsed-date.pipe.ts","../../pipes/src/apply-field-template.ts","../../pipes/src/attribute-value.pipe.ts","../../pipes/src/raw-attribute-value.pipe.ts","../../pipes/src/reference-display-value.pipe.ts","../../pipes/src/reference-attr-value.pipe.ts","../../pipes/src/reference-link-route.pipe.ts","../../pipes/src/reference-chips.pipe.ts","../../pipes/src/router-link.pipe.ts","../../pipes/src/as-detail-type.pipe.ts","../../pipes/src/as-detail-columns.pipe.ts","../../pipes/src/as-detail-row-renderer.pipe.ts","../../pipes/src/as-detail-cell-value.pipe.ts","../../pipes/src/as-detail-display-value.pipe.ts","../../pipes/src/can-create-detail-row.pipe.ts","../../pipes/src/can-delete-detail-row.pipe.ts","../../pipes/src/can-edit-detail-row.pipe.ts","../../pipes/src/lookup-display-type.pipe.ts","../../pipes/src/lookup-display-value.pipe.ts","../../pipes/src/lookup-options.pipe.ts","../../pipes/src/inline-ref-options.pipe.ts","../../pipes/src/error-for-attribute.pipe.ts","../../pipes/src/icon-name.pipe.ts","../../pipes/src/array-value.pipe.ts","../../pipes/src/query-cell.pipe.ts"],"sourcesContent":["import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\n\n@Pipe({ name: 't', pure: false, standalone: true })\nexport class TranslateKeyPipe implements PipeTransform {\n private readonly lang = inject(SparkLanguageService);\n\n transform(key: string): string {\n return this.lang.t(key);\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { TranslatedString, resolveTranslation } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'resolveTranslation', standalone: true, pure: false })\nexport class ResolveTranslationPipe implements PipeTransform {\n transform(value: TranslatedString | undefined, fallback?: string): string {\n return resolveTranslation(value) || fallback || '';\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'inputType', standalone: true, pure: true })\nexport class InputTypePipe implements PipeTransform {\n transform(dataType: string): string {\n switch (dataType) {\n case 'number':\n case 'decimal':\n return 'number';\n case 'boolean':\n return 'checkbox';\n case 'datetime':\n return 'datetime-local';\n case 'date':\n return 'date';\n case 'color':\n return 'color';\n case 'url':\n case 'image':\n // Both hold a URL, so `url` gets the browser's own validation and keyboard. `image` is a\n // link to an image rather than an upload: the value is still a string the author types.\n return 'url';\n default:\n return 'text';\n }\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n/**\n * Parses a wire value into a `Date`, or `null` when it is absent or unparseable.\n *\n * Exists so the detail page and the grid agree. The grid gets its `Date` from `queryCellValue`, which\n * needs a cell and a column and so cannot be reused on a detail attribute; before this, the detail page\n * had no parsing step at all and printed the raw ISO string — `2026-12-31T23:59:00-08:00` in a\n * definition list, while the grid showed `01/01/2027, 08:59` for the same document.\n *\n * The parsing contract is deliberately identical to `queryCellValue`'s: `new Date(...)`, and an\n * unparseable value yields `null` so the caller can fall back to its own text rather than rendering\n * \"Invalid Date\".\n *\n * Note what `new Date(...)` does to an offset, because it is the whole reason this is a separate step:\n * it collapses the value to an instant and forgets the offset it was written with. That is correct for\n * display — a viewer wants the moment in their own zone — but it means a `Date` can never be the thing\n * you reach for when you need the *originating* wall clock. Read that from the wire value instead.\n */\n@Pipe({ name: 'parsedDate', standalone: true, pure: true })\nexport class ParsedDatePipe implements PipeTransform {\n transform(value: unknown): Date | null {\n if (value == null || value === '') return null;\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value !== 'string' && typeof value !== 'number') return null;\n\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? null : parsed;\n }\n}\n","/**\n * Fills a `{Field}` template from a plain object's own fields. Used ONLY for AsDetail\n * nested-object summary display: an embedded value object (no id, no document) is rendered\n * client-side from its own fields. This is deliberately distinct from entity/reference\n * breadcrumbs — those are resolved recursively on the server (BreadcrumbResolver) and the\n * client only reads the resulting strings. Unknown or null placeholders render empty.\n */\nexport function applyFieldTemplate(template: string, data: Record<string, any>): string {\n return template.replace(/\\{(\\w+)\\}/g, (_match, propertyName) => {\n const value = data[propertyName];\n return value != null ? String(value) : '';\n });\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType, LookupReference, PersistentObject, isReservedAsDetailKey, nestedPoToDict, resolveTranslation, resolvedBreadcrumb } from '@mintplayer/ng-spark/models';\nimport { applyFieldTemplate } from './apply-field-template';\n\n@Pipe({ name: 'attributeValue', standalone: true, pure: true })\nexport class AttributeValuePipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null, entityType: EntityType | null, lookupRefOptions: Record<string, LookupReference>, allEntityTypes: EntityType[]): any {\n if (!item) return '';\n const attr = item.attributes.find(a => a.name === attrName);\n if (!attr) return '';\n\n if (attr.breadcrumb) return attr.breadcrumb;\n\n const attrDef = entityType?.attributes.find(a => a.name === attrName);\n if (attrDef?.dataType === 'AsDetail') {\n // Server emits nested PO(s) in attr.objects (array) / attr.object (single) — attr.value is null.\n if (attr.isArray) {\n const count = attr.objects?.length ?? 0;\n if (count === 0) return '';\n return `${count} item${count !== 1 ? 's' : ''}`;\n }\n if (attr.object) {\n // The server has already resolved this object's breadcrumb, and it can resolve templates\n // this side cannot. A type's breadcrumb may name a computed property that `[IgnoreProperty]`\n // keeps out of the model — HR's `Address.Crumb` is exactly that, `[Breadcrumb, IgnoreProperty]`\n // — so the template `{Crumb}` has no matching attribute here and never will. Recomputing it\n // client-side produced an empty string and fell through to \"(object)\" while the correct\n // \"Voorbeeldstraat 1, 1000 Brussel\" sat unread on `attr.object.breadcrumb`.\n //\n // One value on that property is NOT a breadcrumb: when the template renders blank the\n // server substitutes the CLR type name, and printing it reads as data — a `Build` whose\n // `Feedback.State` was unset showed the literal \"BuildFeedback\" (#384). Filtering it here\n // lets the fallbacks below run, which is what they are for.\n const resolved = resolvedBreadcrumb(attr.object.breadcrumb, attrDef.asDetailType);\n if (resolved) return resolved;\n return this.formatAsDetailValue(attrDef, nestedPoToDict(attr.object), allEntityTypes);\n }\n }\n\n if (attrDef?.lookupReferenceType && attr.value != null && attr.value !== '') {\n const lookupRef = lookupRefOptions[attrDef.lookupReferenceType];\n if (lookupRef) {\n const option = lookupRef.values.find(v => v.key === String(attr.value));\n if (option) {\n return resolveTranslation(option.values) || option.key;\n }\n }\n }\n\n if (attrDef?.dataType === 'boolean') {\n return attr.value ?? null;\n }\n\n return attr.value ?? '';\n }\n\n private formatAsDetailValue(attrDef: EntityAttributeDefinition, value: Record<string, any>, allEntityTypes: EntityType[]): string {\n const asDetailType = allEntityTypes.find(t => t.clrType === attrDef.asDetailType);\n\n if (asDetailType?.breadcrumb) {\n const result = applyFieldTemplate(asDetailType.breadcrumb, value);\n if (result && result.trim()) return result;\n }\n\n // Last resort, reached when the type declares no breadcrumb or its template resolved to\n // nothing. Joining the scalar values is always more use to a reader than \"(object)\", which\n // named the failure rather than the row and looked identical for every unresolvable cell.\n // Reserved keys are skipped: `nestedPoToDict` stashes the row key and the row's own breadcrumb\n // in this same dict, and joining those prints a guid and a placeholder as though they were\n // fields. Previously masked -- this branch was only reachable when the server sent no\n // breadcrumb, which is exactly when there was nothing stashed to leak.\n return Object.entries(value)\n .filter(([k, v]) => !isReservedAsDetailKey(k) && v != null && typeof v !== 'object' && String(v).trim() !== '')\n .map(([, v]) => v)\n .join(', ');\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { PersistentObject } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'rawAttributeValue', standalone: true, pure: true })\nexport class RawAttributeValuePipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null): any {\n return item?.attributes.find(a => a.name === attrName)?.value;\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { EntityAttributeDefinition, PersistentObject, QueryResultItem } from '@mintplayer/ng-spark/models';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\n\n@Pipe({ name: 'referenceDisplayValue', standalone: true, pure: true })\nexport class ReferenceDisplayValuePipe implements PipeTransform {\n private readonly lang = inject(SparkLanguageService);\n\n transform(attr: EntityAttributeDefinition, formData: Record<string, any>, referenceOptions: Record<string, QueryResultItem[]>): string {\n const selectedId = formData[attr.name];\n if (!selectedId) return this.lang.t('common.notSelected');\n\n const options = referenceOptions[attr.name] || [];\n const selected = options.find(o => o.id === selectedId);\n // The server resolves a row's display string into `breadcrumb`; falling back to the id\n // beats showing nothing, and there is no third source now that a row carries no `name`.\n return selected?.breadcrumb || selectedId;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { SparkRow, valueFor } from '@mintplayer/ng-spark/models';\n\n/**\n * The text a reference picker's result grid shows in one column.\n *\n * ⚠️ **Reads the row through {@link valueFor}, not through a shape it assumes.** This used to do\n * `item.attributes.find(...)`, which is the `PersistentObject` shape — but a picker's rows are\n * `QueryResultItem`s, which carry `values` (`[{ key, value, breadcrumb }]`) and have no `attributes`\n * at all. Every cell therefore threw `can't access property \"find\", item.attributes is undefined`,\n * and the picker rendered nothing selectable.\n *\n * There are three row shapes in this codebase and `valueFor` exists precisely so a consumer does not\n * have to know which one it was handed. Using it is also what keeps this working if a picker is ever\n * pointed at an AsDetail sub-table, whose rows are the flat record shape.\n */\n@Pipe({ name: 'referenceAttrValue', standalone: true, pure: true })\nexport class ReferenceAttrValuePipe implements PipeTransform {\n transform(item: SparkRow, attrName: string): any {\n const cell = valueFor(item, attrName);\n if (!cell) return '';\n // Breadcrumb first: a reference cell's value is an id, and the id is not what a person reads.\n if (cell.breadcrumb) return cell.breadcrumb;\n return cell.value ?? '';\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityType } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'referenceLinkRoute', standalone: true, pure: true })\nexport class ReferenceLinkRoutePipe implements PipeTransform {\n transform(referenceClrType: string, referenceId: any, allEntityTypes: EntityType[]): string[] | null {\n if (!referenceId || !referenceClrType) return null;\n const targetType = allEntityTypes.find(t => t.clrType === referenceClrType);\n if (!targetType) return null;\n\n // Presence in the catalogue means the caller may LIST this type; opening one is a different\n // right. Without this a type granted Query but not Read rendered a clickable reference whose\n // click refused on arrival — while the grid's own first column, three components away, gated on\n // canRead and got it right. Same question, two answers.\n if (targetType.canRead === false) return null;\n\n return ['/po', targetType.alias || targetType.id, referenceId];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { PersistentObject } from '@mintplayer/ng-spark/models';\n\nexport interface ReferenceChip {\n id: string;\n label: string;\n}\n\n/**\n * Resolves a multi-reference attribute (`dataType === 'Reference'`, `isArray === true`)\n * to a list of chips. The attribute's `value` carries the id array; `breadcrumbs` (a\n * server-resolved id → label map) provides each chip's display label, falling back to\n * the id when no breadcrumb is present. Mirrors the single-reference `breadcrumb`\n * display, applied per id.\n */\n@Pipe({ name: 'referenceChips', standalone: true, pure: true })\nexport class ReferenceChipsPipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null): ReferenceChip[] {\n const attr = item?.attributes.find(a => a.name === attrName);\n if (!attr || !Array.isArray(attr.value)) return [];\n const breadcrumbs = attr.breadcrumbs ?? {};\n return attr.value\n .filter(v => v != null && v !== '')\n .map(v => {\n const id = String(v);\n return { id, label: breadcrumbs[id] || id };\n });\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { ProgramUnit } from '@mintplayer/ng-spark/models';\n\n/**\n * Maps a program unit to the router commands that open it. Type comparisons are exact: the\n * server's loader canonicalizes `type` casing, so tolerating variants here would only mask a\n * server that stopped doing so.\n *\n * Returns null for a `url` unit — an external address is an `<a href>`, not a router link; the\n * component rendering the menu owns that branch.\n */\n@Pipe({ name: 'routerLink', standalone: true, pure: true })\nexport class RouterLinkPipe implements PipeTransform {\n transform(unit: ProgramUnit): string[] | null {\n if (unit.type === 'query') {\n return ['/query', unit.alias || unit.queryId!];\n } else if (unit.type === 'persistentObject') {\n const type = unit.alias || unit.persistentObjectId!;\n return unit.objectId ? ['/po', type, unit.objectId] : ['/po', type];\n } else if (unit.type === 'url') {\n return null;\n }\n return ['/'];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'asDetailType', standalone: true, pure: true })\nexport class AsDetailTypePipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, asDetailTypes: Record<string, EntityType>): EntityType | null {\n return asDetailTypes[attr.name] || null;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType } from '@mintplayer/ng-spark/models';\nimport { SparkAttributeRendererRegistration, isRowRendered } from '@mintplayer/ng-spark/renderers';\n\n/**\n * The columns of an AsDetail table: the row type's visible attributes, in order — minus those a\n * registered row renderer draws (`rowComponent`), when the host passes its renderer registry.\n */\n@Pipe({ name: 'asDetailColumns', standalone: true, pure: true })\nexport class AsDetailColumnsPipe implements PipeTransform {\n transform(\n attr: EntityAttributeDefinition,\n asDetailTypes: Record<string, EntityType>,\n renderers?: readonly SparkAttributeRendererRegistration[] | null,\n ): EntityAttributeDefinition[] {\n const type = asDetailTypes[attr.name];\n if (!type) return [];\n return type.attributes\n .filter(a => a.isVisible && !isRowRendered(a, renderers))\n .sort((a, b) => a.order - b.order);\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType } from '@mintplayer/ng-spark/models';\nimport { SparkAttributeRendererRegistration, SparkResolvedRowRenderer, resolveRowRenderer } from '@mintplayer/ng-spark/renderers';\n\n/** The row renderer an AsDetail attribute's row type names (`rowComponent`), or null. */\n@Pipe({ name: 'asDetailRowRenderer', standalone: true, pure: true })\nexport class AsDetailRowRendererPipe implements PipeTransform {\n transform(\n attr: EntityAttributeDefinition,\n asDetailTypes: Record<string, EntityType>,\n renderers: readonly SparkAttributeRendererRegistration[] | null | undefined,\n ): SparkResolvedRowRenderer | null {\n return resolveRowRenderer(asDetailTypes[attr.name], renderers);\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { AS_DETAIL_BREADCRUMBS_KEY, EntityAttributeDefinition, PersistentObject, QueryResultItem, selfBreadcrumb } from '@mintplayer/ng-spark/models';\n\n/** What a cell can resolve to: the column's own type, or `null` when it has no value. */\nexport type AsDetailCellValue = string | number | boolean | Date | null;\n\n/**\n * The value of one cell in an `AsDetail` table, **typed by the column's `dataType`** rather than\n * flattened to a string.\n *\n * <p>\n * This used to end in `return String(value)` for everything, and declare `: string`. That is fine\n * for a cell which is only interpolated, and wrong for one whose consumer inspects it —\n * `spark-grid-cell` branches on the column type and binds `[checked]=\"display() === true\"` and\n * `[indeterminate]=\"display() == null\"` for a boolean, so it needs the value and not a rendering\n * of it.\n * </p>\n * <p>\n * The visible symptom was a detail page showing unticked checkboxes for rows whose edit form showed\n * them ticked — the edit form reads `attribute.objects` directly, so it never went through here. All\n * three states of a `bool?` were affected, and one of them silently:\n * </p>\n * <ul>\n * <li><b>true</b> — `'true' === true` is false, so it rendered <b>unchecked</b>;</li>\n * <li><b>null</b> — became `''`, and `'' == null` is false, so it rendered unchecked <b>and\n * determinate</b>, i.e. claiming to be false;</li>\n * <li><b>false</b> — `'false'` is not `=== true`, so it rendered correctly <b>by accident</b>.</li>\n * </ul>\n * <p>\n * Types that stay strings do so deliberately, not by omission. `color`, `image` and `url` are bound\n * into a style, an `src` and an `href`, so a string is what they need. `Reference` and `AsDetail`\n * resolve to a breadcrumb, which is a label rather than a value.\n * </p>\n * <p>\n * `date` and `datetime` return a real `Date`. They arrive as ISO strings, and the cell used to\n * print that string verbatim — `2026-09-07T13:25:57.3119825Z` in a table column. Returning the\n * parsed value lets `spark-grid-cell` format it in one place for both grids; an unparseable value\n * falls back to its text, because showing what is there beats showing `Invalid Date`.\n * </p>\n */\n@Pipe({ name: 'asDetailCellValue', standalone: true, pure: true })\nexport class AsDetailCellValuePipe implements PipeTransform {\n transform(\n row: Record<string, any>,\n parentAttr: EntityAttributeDefinition,\n col: EntityAttributeDefinition,\n asDetailRefOptions: Record<string, Record<string, QueryResultItem[]>>,\n ): AsDetailCellValue {\n const value = row[col.name];\n\n // Before the null short-circuit, because for these types \"absent\" is a value the consumer has\n // to be able to see: `null` is what makes a checkbox indeterminate rather than false, and it is\n // what distinguishes \"no number\" from the number zero. `''` erases both.\n switch (col.dataType) {\n case 'boolean':\n return typeof value === 'boolean' ? value : null;\n\n case 'number':\n case 'decimal': {\n if (value == null || value === '') return null;\n const asNumber = typeof value === 'number' ? value : Number(value);\n // A value that is not a number is passed through as text rather than reported as NaN: the\n // cell's job is to show what is there, and \"NaN\" tells the reader nothing about it.\n return Number.isNaN(asNumber) ? String(value) : asNumber;\n }\n\n case 'date':\n case 'datetime': {\n if (value == null || value === '') return null;\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n const parsed = new Date(value as string);\n // Same rule as the number branch: an unparseable date shows its own text rather than\n // \"Invalid Date\", which would hide the value that is actually stored.\n return Number.isNaN(parsed.getTime()) ? String(value) : parsed;\n }\n }\n\n if (value == null) return '';\n\n if (col.dataType === 'Reference' && col.query) {\n // Prefer the breadcrumb the server already resolved by id — it is page-independent, so it\n // renders the label even when the referenced document falls outside the reference query's\n // first options page (issue #185).\n const serverBreadcrumb = (row[AS_DETAIL_BREADCRUMBS_KEY] as Record<string, string> | undefined)?.[col.name];\n if (serverBreadcrumb) return serverBreadcrumb;\n\n // Fallback: resolve against the loaded options page (legacy path).\n const parentOptions = asDetailRefOptions[parentAttr.name];\n if (parentOptions) {\n const options = parentOptions[col.name];\n if (options) {\n const match = options.find(o => o.id === value);\n if (match) return match.breadcrumb || String(value);\n }\n }\n }\n\n // A nested-AsDetail column's value is an inner dict, so String(value) produced \"[object\n // Object]\". The flattener kept that object's server-resolved breadcrumb for exactly this.\n if (col.dataType === 'AsDetail' && typeof value === 'object') {\n return selfBreadcrumb(value as Record<string, any>, col.asDetailType) ?? '';\n }\n\n return String(value);\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { EntityAttributeDefinition, EntityType, selfBreadcrumb } from '@mintplayer/ng-spark/models';\nimport { SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport { applyFieldTemplate } from './apply-field-template';\n\n@Pipe({ name: 'asDetailDisplayValue', standalone: true, pure: true })\nexport class AsDetailDisplayValuePipe implements PipeTransform {\n private readonly lang = inject(SparkLanguageService);\n\n transform(attr: EntityAttributeDefinition, formData: Record<string, any>, asDetailTypes: Record<string, EntityType>): string {\n const value = formData[attr.name];\n if (!value) return this.lang.t('common.notSet');\n\n const asDetailType = asDetailTypes[attr.name] || null;\n\n // The server's own resolution wins, because it can render templates this side cannot: a\n // breadcrumb may name a property that `[IgnoreProperty]` deliberately keeps out of the model\n // (HR's `Address.Crumb`), and no amount of client-side substitution will ever find it. Passing\n // the type name filters out the mapper's \"template rendered blank\" placeholder, which is the\n // bare type name rather than an empty string — see `selfBreadcrumb`.\n const resolved = selfBreadcrumb(value, asDetailType?.name);\n if (resolved) return resolved;\n\n // Still correct for a template naming real, projected attributes (`\"{Street}, {City}\"`), which\n // is the common case and the only one reachable on the create path, where there is no server\n // object to have resolved anything yet.\n if (asDetailType?.breadcrumb) {\n const result = applyFieldTemplate(asDetailType.breadcrumb, value);\n if (result && result.trim()) return result;\n }\n\n return this.lang.t('common.clickToEdit');\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityPermissions } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'canCreateDetailRow', standalone: true, pure: true })\nexport class CanCreateDetailRowPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, permissions: Record<string, EntityPermissions>): boolean {\n const perms = permissions[attr.name];\n // Fail closed. An absent entry means the child type's permissions were never fetched, not that\n // the user may do this -- and since the save path now enforces `New/{RowType}`, defaulting\n // to true renders a button whose save is refused. The fetch is also not guaranteed: it used to\n // go through the entity-type catalogue, which is Query-gated, so a user with `New` but not\n // `Query` never got an entry at all.\n return perms ? perms.canCreate : false;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityPermissions } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'canDeleteDetailRow', standalone: true, pure: true })\nexport class CanDeleteDetailRowPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, permissions: Record<string, EntityPermissions>): boolean {\n const perms = permissions[attr.name];\n // Fail closed. An absent entry means the child type's permissions were never fetched, not that\n // the user may do this -- and since the save path now enforces `Delete/{RowType}`, defaulting\n // to true renders a button whose save is refused. The fetch is also not guaranteed: it used to\n // go through the entity-type catalogue, which is Query-gated, so a user with `Delete` but not\n // `Query` never got an entry at all.\n return perms ? perms.canDelete : false;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, EntityPermissions } from '@mintplayer/ng-spark/models';\n\n/**\n * Whether the caller may edit rows of an AsDetail attribute's row type.\n *\n * The third of the `can*DetailRow` family, and the one the server was already answering. `canEdit`\n * has been computed, shipped and fetched per AsDetail attribute since row rights landed — it was\n * simply never read for the row path, so the form offered an edit the save would undo.\n *\n * ⚠️ Apply this **read-only, never hidden**. A row the caller may not edit must stay fully\n * readable; hiding the inputs would hide the data, which is a worse answer than showing it\n * uneditable.\n */\n@Pipe({ name: 'canEditDetailRow', standalone: true, pure: true })\nexport class CanEditDetailRowPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, permissions: Record<string, EntityPermissions>): boolean {\n const perms = permissions[attr.name];\n // Fail closed, for the same reason as its two siblings: an absent entry means the row type's\n // permissions were never fetched, not that the user may edit. And the save path enforces\n // `Edit/{RowType}` independently -- it silently restores the stored row and warns -- so\n // defaulting to true offers an edit whose result is discarded.\n return perms ? perms.canEdit : false;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { ELookupDisplayType, EntityAttributeDefinition, LookupReference } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'lookupDisplayType', standalone: true, pure: true })\nexport class LookupDisplayTypePipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, lookupRefOptions: Record<string, LookupReference>): ELookupDisplayType {\n const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;\n return lookupRef?.displayType ?? ELookupDisplayType.Dropdown;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, LookupReference, resolveTranslation } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'lookupDisplayValue', standalone: true, pure: true })\nexport class LookupDisplayValuePipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, formData: Record<string, any>, lookupRefOptions: Record<string, LookupReference>): string {\n const currentValue = formData[attr.name];\n if (currentValue == null || currentValue === '') return '';\n\n const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;\n const options = lookupRef?.values.filter(v => v.isActive) || [];\n const selected = options.find(o => o.key === String(currentValue));\n if (!selected) return String(currentValue);\n\n return resolveTranslation(selected.values) || selected.key;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, LookupReference, LookupReferenceValue } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'lookupOptions', standalone: true, pure: true })\nexport class LookupOptionsPipe implements PipeTransform {\n transform(attr: EntityAttributeDefinition, lookupRefOptions: Record<string, LookupReference>): LookupReferenceValue[] {\n const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;\n return lookupRef?.values.filter(v => v.isActive) || [];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EntityAttributeDefinition, PersistentObject, QueryResultItem } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'inlineRefOptions', standalone: true, pure: true })\nexport class InlineRefOptionsPipe implements PipeTransform {\n transform(parentAttr: EntityAttributeDefinition, col: EntityAttributeDefinition, asDetailRefOptions: Record<string, Record<string, QueryResultItem[]>>): QueryResultItem[] {\n return asDetailRefOptions[parentAttr.name]?.[col.name] || [];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { ValidationError, resolveTranslation } from '@mintplayer/ng-spark/models';\n\n@Pipe({ name: 'errorForAttribute', standalone: true, pure: true })\nexport class ErrorForAttributePipe implements PipeTransform {\n transform(attrName: string, validationErrors: ValidationError[]): string | null {\n const error = validationErrors.find(e => e.attributeName === attrName);\n return error ? resolveTranslation(error.errorMessage) : null;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'iconName', standalone: true, pure: true })\nexport class IconNamePipe implements PipeTransform {\n transform(value: string | undefined, fallback: string): string {\n const iconClass = value || fallback;\n // Strip 'bi-' prefix if present\n return iconClass.startsWith('bi-') ? iconClass.substring(3) : iconClass;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { PersistentObject, nestedPoToDisplayRow } from '@mintplayer/ng-spark/models';\n\n/**\n * Resolves an attribute to a list of flat row dicts for the detail-page table.\n * AsDetail array attributes carry their data as nested PersistentObjects in\n * <c>attr.objects</c>, not <c>attr.value</c> — the server stopped putting flat\n * dicts in <c>value</c> when AsDetail moved to its dedicated wire shape. Without\n * this branch, the detail page rendered AsDetail arrays as empty tables even\n * when the edit page (which reads <c>attr.objects</c> directly) showed rows.\n */\n@Pipe({ name: 'arrayValue', standalone: true, pure: true })\nexport class ArrayValuePipe implements PipeTransform {\n transform(attrName: string, item: PersistentObject | null): Record<string, any>[] {\n const attr = item?.attributes.find(a => a.name === attrName);\n if (!attr) return [];\n if (attr.dataType === 'AsDetail' && attr.isArray && Array.isArray(attr.objects)) {\n return attr.objects.map(po => nestedPoToDisplayRow(po));\n }\n if (Array.isArray(attr.value)) return attr.value;\n return [];\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport {\n LookupReference,\n QueryColumn,\n QueryResultItem,\n resolveTranslation,\n resolvedBreadcrumb,\n valueFor,\n} from '@mintplayer/ng-spark/models';\nimport { ReferenceChip } from './reference-chips.pipe';\n\n/**\n * The display text of one query-grid cell.\n *\n * The attribute-shaped sibling of this ({@link AttributeValuePipe}) still serves detail and edit\n * pages, which do work in attributes. They are deliberately not one pipe: a query row is a flat\n * projection with no nested objects and no per-row metadata, so the fallbacks that make sense\n * there — reaching into `attr.object`, recomputing a breadcrumb template client-side — are not\n * merely unnecessary here, they are unreachable. Sharing the code would mean carrying branches\n * that can never fire and inviting the next reader to feed one shape into the other.\n */\n@Pipe({ name: 'queryCellValue', standalone: true, pure: true })\nexport class QueryCellValuePipe implements PipeTransform {\n transform(\n column: QueryColumn,\n item: QueryResultItem | null,\n lookupRefOptions: Record<string, LookupReference>,\n ): any {\n const cell = valueFor(item, column.name);\n if (!cell) return '';\n\n // A resolved reference display wins: the server can resolve breadcrumb templates this side\n // cannot (a template may name a computed property `[IgnoreProperty]` keeps out of the model).\n // Except the CLR type name, which is the server's placeholder for a template that rendered\n // blank, not a label (#384). `asDetailType` is undefined on every other kind of column, so\n // nothing else is filtered.\n const resolved = resolvedBreadcrumb(cell.breadcrumb, column.asDetailType);\n if (resolved) return resolved;\n\n if (column.dataType === 'AsDetail') {\n // An array cell carries a child COUNT rather than the children, so the wording — and its\n // pluralisation — is decided here, where the language is. A single-child cell carries the\n // child object (for a renderer to read) and is displayed by the server-resolved breadcrumb\n // above, never by stringifying it here.\n if (!column.isArray) return '';\n const count = typeof cell.value === 'number' ? cell.value : 0;\n return count === 0 ? '' : `${count} item${count !== 1 ? 's' : ''}`;\n }\n\n if (column.lookupReferenceType && cell.value != null && cell.value !== '') {\n const lookupRef = lookupRefOptions[column.lookupReferenceType];\n const option = lookupRef?.values.find(v => v.key === String(cell.value));\n if (option) return resolveTranslation(option.values) || option.key;\n }\n\n // Null rather than '' so the cell can tell \"false\" from \"unset\" and render an indeterminate\n // checkbox instead of an unchecked one.\n if (column.dataType === 'boolean') return cell.value ?? null;\n\n // Dates are parsed here for the same reason booleans are passed through above: the cell needs\n // the value, not a rendering of it. Both grids feed `spark-grid-cell`, so both have to agree on\n // the type or the same `datetime` column formats one way in a query grid and another in an\n // AsDetail table. Unparseable input keeps its own text rather than becoming \"Invalid Date\".\n if (column.dataType === 'date' || column.dataType === 'datetime') {\n if (cell.value == null || cell.value === '') return null;\n if (cell.value instanceof Date) return Number.isNaN(cell.value.getTime()) ? null : cell.value;\n const parsed = new Date(cell.value as string);\n return Number.isNaN(parsed.getTime()) ? String(cell.value) : parsed;\n }\n\n return cell.value ?? '';\n }\n}\n\n/**\n * Resolves a multi-reference column (`dataType === 'Reference'`, `isArray === true`) to chips.\n *\n * The cell's `value` carries the id array and `breadcrumbs` the server-resolved id → label map,\n * falling back to the id itself when a label is missing — the same display rule as a single\n * reference, applied per id.\n */\n@Pipe({ name: 'queryReferenceChips', standalone: true, pure: true })\nexport class QueryReferenceChipsPipe implements PipeTransform {\n transform(column: QueryColumn, item: QueryResultItem | null): ReferenceChip[] {\n const cell = valueFor(item, column.name);\n if (!cell || !Array.isArray(cell.value)) return [];\n\n const breadcrumbs = cell.breadcrumbs ?? {};\n return cell.value\n .filter(v => v != null && v !== '')\n .map(v => {\n const id = String(v);\n return { id, label: breadcrumbs[id] || id };\n });\n }\n}\n"],"mappings":"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"}
@@ -17,11 +17,11 @@ import { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } f
17
17
  import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
18
18
  import { SparkLanguageService, SparkReturnNavigationService, SparkService } from "@mintplayer/ng-spark/services";
19
19
  import { SparkAttributeRefreshService, SparkQueryRefreshService } from "@mintplayer/ng-spark/client-operations";
20
- import { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AttributeValuePipe, ParsedDatePipe, RawAttributeValuePipe, ReferenceChipsPipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, TranslateKeyPipe } from "@mintplayer/ng-spark/pipes";
20
+ import { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailRowRendererPipe, AttributeValuePipe, ParsedDatePipe, RawAttributeValuePipe, ReferenceChipsPipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, TranslateKeyPipe } from "@mintplayer/ng-spark/pipes";
21
21
  import { SparkIconComponent } from "@mintplayer/ng-spark/icon";
22
22
  import { SparkAttributeDescriptionComponent } from "@mintplayer/ng-spark/attribute-description";
23
23
  import { SparkGridCellComponent, SparkQueryCardComponent } from "@mintplayer/ng-spark/grid";
24
- import { SPARK_ATTRIBUTE_RENDERERS, rendererValue, withDeclaredInputs } from "@mintplayer/ng-spark/renderers";
24
+ import { SPARK_ATTRIBUTE_RENDERERS, rendererValue, rowRendererInputs, withDeclaredInputs } from "@mintplayer/ng-spark/renderers";
25
25
  import { ShowedOn, filterDetailActions, hasShowedOnFlag, subQueriesOf } from "@mintplayer/ng-spark/models";
26
26
  import { SPARK_DETAIL_ACTIONS, SPARK_DETAIL_PANELS, orderSparkExtensions, parseSparkDeletedParam } from "@mintplayer/ng-spark/panels";
27
27
  import { combineLatest } from "rxjs";
@@ -196,6 +196,9 @@ var SparkPoDetailComponent = class SparkPoDetailComponent {
196
196
  if (!attr.renderer) return null;
197
197
  return this.rendererRegistry.find((r) => r.name === attr.renderer)?.detailComponent ?? null;
198
198
  }
199
+ getRowRendererInputs(renderer, row, item) {
200
+ return rowRendererInputs(renderer, row, item?.id);
201
+ }
199
202
  getDetailRendererInputs(component, attr, item) {
200
203
  const itemAttr = item.attributes.find((a) => a.name === attr.name);
201
204
  const formData = {};
@@ -383,8 +386,8 @@ var SparkPoDetailComponent = class SparkPoDetailComponent {
383
386
  customActionExecuted: "customActionExecuted"
384
387
  },
385
388
  ngImport: i0,
386
- template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
387
- styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--bs-tertiary-bg);border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"],
389
+ template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
390
+ styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"],
388
391
  dependencies: [
389
392
  {
390
393
  kind: "ngmodule",
@@ -640,6 +643,11 @@ var SparkPoDetailComponent = class SparkPoDetailComponent {
640
643
  type: AsDetailColumnsPipe,
641
644
  name: "asDetailColumns"
642
645
  },
646
+ {
647
+ kind: "pipe",
648
+ type: AsDetailRowRendererPipe,
649
+ name: "asDetailRowRenderer"
650
+ },
643
651
  {
644
652
  kind: "pipe",
645
653
  type: AsDetailCellValuePipe,
@@ -706,6 +714,7 @@ i0.ɵɵngDeclareClassMetadata({
706
714
  AttributeValuePipe,
707
715
  RawAttributeValuePipe,
708
716
  AsDetailColumnsPipe,
717
+ AsDetailRowRendererPipe,
709
718
  AsDetailCellValuePipe,
710
719
  ArrayValuePipe,
711
720
  ReferenceLinkRoutePipe,
@@ -714,8 +723,8 @@ i0.ɵɵngDeclareClassMetadata({
714
723
  SparkAttributeDescriptionComponent
715
724
  ],
716
725
  changeDetection: ChangeDetectionStrategy.OnPush,
717
- template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
718
- styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--bs-tertiary-bg);border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"]
726
+ template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"spark-actionbar py-2\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <button *bsPriorityNavItem=\"1\" class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'common.back' | t }}\n </button>\n @if (canEdit()) {\n <button *bsPriorityNavItem=\"2\" class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'common.edit' | t }}\n </button>\n }\n @if (canDelete()) {\n <button *bsPriorityNavItem=\"3\" class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'common.delete' | t }}\n </button>\n }\n @if (showCustomActions()) {\n @for (action of visibleCustomActions(); track action.name) {\n <button *bsPriorityNavItem=\"10 + action.offset\" [class]=\"customActionClass(action)\" [disabled]=\"runningAction() !== null\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *bsPriorityNavItem=\"50\">\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n </ng-container>\n }\n <!-- Add-on actions (SPARK_DETAIL_ACTIONS): each decides for itself whether it applies. -->\n @if (detailContext(); as ctx) {\n @for (action of detailActions; track action.id) {\n <ng-container *bsPriorityNavItem=\"action.priority ?? 60\">\n <ng-container *ngComponentOutlet=\"action.component; inputs: { context: ctx }\" />\n </ng-container>\n }\n }\n </bs-priority-nav>\n </div>\n <h2>\n {{ currentItem.breadcrumb || currentItem.name }}\n @if (isDeletedView()) {\n <bs-badge [type]=\"colors.secondary\" class=\"align-middle ms-2 spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}<spark-attribute-description [description]=\"attr.description\" /></dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(rendererType, attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}<spark-attribute-description [description]=\"col.description\" /></th>\n }\n </tr>\n </thead>\n <tbody class=\"align-middle\">\n @let rowRenderer = (attr | asDetailRowRenderer:asDetailTypes():rendererRegistry);\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes():rendererRegistry); track col.name; let firstCol = $first) {\n <td>\n <!--\n Shared with the query grid. This cell used to hand-roll the renderer\n dispatch and print everything else as text, so a `boolean` column read\n \"true\" here and rendered a checkbox in a grid, and a `color` showed its\n hex. Only the VALUE is resolved locally: these rows are plain\n dictionaries, not PersistentObjects.\n\n `chips` is left unset for a reference array on purpose — an AsDetail\n row's `__sparkBreadcrumbs` is keyed by column, not by id, so there are\n no labels for the members. Text beats a row of raw ids.\n -->\n <spark-grid-cell\n [column]=\"col\"\n [display]=\"(row | asDetailCellValue:attr:col:asDetailReferenceOptions())\"\n [rendererValue]=\"row[col.name]\"\n [item]=\"row\"\n [link]=\"col.dataType === 'Reference' && col.referenceType\n ? (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes())\n : null\" />\n <!-- A row renderer (rowComponent: contributions' attribution line) draws\n its attributes once, under the row's first cell, instead of as columns. -->\n @if (firstCol && rowRenderer) {\n <div class=\"spark-row-renderer\">\n <ng-container *ngComponentOutlet=\"rowRenderer.component; inputs: getRowRendererInputs(rowRenderer, row, currentItem)\"></ng-container>\n </div>\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes():rendererRegistry).length\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n class=\"opacity-100\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"spark-color-swatch d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'image') {\n @let imageSrc = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (imageSrc) {\n <img [src]=\"imageSrc\" [alt]=\"(attr.label | resolveTranslation) || attr.name\" loading=\"lazy\"\n class=\"img-fluid rounded\" style=\"max-height: 12rem;\">\n } @else {\n -\n }\n } @else if (attr.dataType === 'url') {\n @let hrefVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (hrefVal) {\n <a [href]=\"hrefVal\" target=\"_blank\" rel=\"noopener noreferrer\">{{ hrefVal }}</a>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n @let chips = (attr.name | referenceChips:item());\n @if (chips.length) {\n <div class=\"d-flex flex-wrap gap-1\">\n @for (chip of chips; track chip.id) {\n @let chipRoute = (attr.referenceType ? (attr.referenceType | referenceLinkRoute:chip.id:allEntityTypes()) : null);\n @if (chipRoute) {\n <a class=\"spark-chip\" [routerLink]=\"chipRoute\">{{ chip.label }}</a>\n } @else {\n <span class=\"spark-chip\">{{ chip.label }}</span>\n }\n }\n </div>\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else if (attr.dataType === 'MultiLineString') {\n <div class=\"spark-multiline-value\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}</div>\n } @else if (attr.dataType === 'date' || attr.dataType === 'datetime') {\n <!--\n Formatted the same way, and in the same timezone, as the grid renders it.\n\n Before this, a detail page printed the raw wire value — `2026-12-31T23:59:00-08:00`\n in a definition list — while the grid showed `01/01/2027, 08:59` for the same\n document. Not a rounding difference: a DateTimeOffset whose offset differs from the\n viewer's can disagree on the DATE, so the two pages named different days for one car.\n\n The grid's meaning wins because it is the one a reader can act on: a timestamp is an\n instant, shown in the viewer's own zone. The originating offset is still on the wire\n for code that needs it — server-side logic, exports, \"which country's morning was\n this?\" — it simply is not what a viewer is shown by default.\n -->\n @let when = (attr.name | rawAttributeValue:item()) | parsedDate;\n @if (when) {\n {{ when | date:(attr.dataType === 'date' ? 'mediumDate' : 'short') }}\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @for (subQuery of subQueries(); track subQuery.query) {\n <!-- The entry's selectionMode overrides the query's own for this parent (#460, D17). -->\n <spark-query-card [queryId]=\"subQuery.query\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\"\n [selectionMode]=\"subQuery.selectionMode ?? null\"\n [parentDeleted]=\"isDeletedView()\"\n [iconTemplate]=\"queryIconTemplate()\" [captionTemplate]=\"queryCaptionTemplate()\"\n [actionsTemplate]=\"queryActionsTemplate()\" />\n }\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n <!-- Add-on panels (SPARK_DETAIL_PANELS), e.g. <spark-po-history>. -->\n @if (detailContext(); as ctx) {\n @for (panel of detailPanels; track panel.id) {\n <div class=\"spark-detail-panel m-3\">\n <ng-container *ngComponentOutlet=\"panel.component; inputs: { context: ctx }\" />\n </div>\n }\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n",
727
+ styles: [".spark-actionbar{--_bleed-block: var(--spark-content-inset-block, 0px);--_bleed-inline: var(--spark-content-inset-inline, 0px);--_gutter: var(--spark-actionbar-gutter, 1rem);position:sticky;top:calc(-1 * var(--_bleed-block));z-index:400;background-color:var(--spark-shell-main-bg, var(--bs-body-bg));border-bottom:1px solid var(--bs-border-color);margin-bottom:1rem;margin-inline:calc(-1 * var(--_bleed-inline));padding-inline:calc(var(--_bleed-inline) + var(--_gutter))}.spark-actionbar .btn{border-radius:0}.spark-actionbar-wide{--spark-actionbar-gutter: 1.5rem}.spark-chip{display:inline-block;padding:.35em .9em;font-size:.8125em;font-weight:600;line-height:1.4;white-space:nowrap;text-align:center;vertical-align:baseline;color:var(--bs-body-color);background-color:var(--bs-secondary-bg);border:1px solid var(--bs-border-color);border-radius:50rem;text-decoration:none}a.spark-chip:hover,a.spark-chip:focus-visible{background-color:var(--bs-tertiary-bg)}.spark-color-swatch{width:1.5em;height:1.5em}.container{--spark-content-inset-inline: 0px}.spark-multiline-value{white-space:pre-wrap}\n"]
719
728
  }]
720
729
  }],
721
730
  ctorParameters: () => [],