@mintplayer/ng-spark 22.31.0 → 22.33.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 (75) hide show
  1. package/README.md +101 -1
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +3 -3
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +18 -18
  4. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +16 -8
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +3 -3
  7. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +6 -6
  8. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +3 -3
  9. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +3 -3
  10. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +3 -3
  11. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
  12. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +3 -3
  13. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +3 -3
  14. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +3 -3
  15. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +3 -3
  16. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +24 -24
  17. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +3 -3
  18. package/fesm2022/mintplayer-ng-spark-contributions.mjs +9 -9
  19. package/fesm2022/mintplayer-ng-spark-grid.mjs +220 -49
  20. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  21. package/fesm2022/mintplayer-ng-spark-history.mjs +9 -9
  22. package/fesm2022/mintplayer-ng-spark-icon.mjs +3 -3
  23. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +3 -3
  24. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +9 -9
  25. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +6 -6
  26. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +3 -3
  27. package/fesm2022/mintplayer-ng-spark-models.mjs +44 -1
  28. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  29. package/fesm2022/mintplayer-ng-spark-moderation.mjs +21 -21
  30. package/fesm2022/mintplayer-ng-spark-notifications.mjs +687 -0
  31. package/fesm2022/mintplayer-ng-spark-notifications.mjs.map +1 -0
  32. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs +179 -0
  33. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs.map +1 -0
  34. package/fesm2022/mintplayer-ng-spark-organizations.mjs +202 -0
  35. package/fesm2022/mintplayer-ng-spark-organizations.mjs.map +1 -0
  36. package/fesm2022/mintplayer-ng-spark-pipes.mjs +125 -85
  37. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  38. package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -3
  39. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +90 -26
  40. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  41. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +84 -12
  42. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  43. package/fesm2022/mintplayer-ng-spark-po-form.mjs +9 -9
  44. package/fesm2022/mintplayer-ng-spark-pwa.mjs +74 -6
  45. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -1
  46. package/fesm2022/mintplayer-ng-spark-query-list.mjs +10 -109
  47. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  48. package/fesm2022/mintplayer-ng-spark-renderers.mjs +3 -3
  49. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +3 -3
  50. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs +801 -0
  51. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs.map +1 -0
  52. package/fesm2022/mintplayer-ng-spark-services.mjs +424 -72
  53. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  54. package/fesm2022/mintplayer-ng-spark-share-page.mjs +334 -0
  55. package/fesm2022/mintplayer-ng-spark-share-page.mjs.map +1 -0
  56. package/fesm2022/mintplayer-ng-spark-share.mjs +10 -0
  57. package/fesm2022/mintplayer-ng-spark-share.mjs.map +1 -0
  58. package/fesm2022/mintplayer-ng-spark-shell.mjs +38 -36
  59. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  60. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +9 -9
  61. package/package.json +25 -1
  62. package/types/mintplayer-ng-spark-grid.d.ts +77 -26
  63. package/types/mintplayer-ng-spark-models.d.ts +150 -17
  64. package/types/mintplayer-ng-spark-notifications.d.ts +188 -0
  65. package/types/mintplayer-ng-spark-organizations-accept.d.ts +40 -0
  66. package/types/mintplayer-ng-spark-organizations.d.ts +72 -0
  67. package/types/mintplayer-ng-spark-pipes.d.ts +10 -0
  68. package/types/mintplayer-ng-spark-po-detail.d.ts +26 -0
  69. package/types/mintplayer-ng-spark-po-edit.d.ts +37 -0
  70. package/types/mintplayer-ng-spark-pwa.d.ts +48 -2
  71. package/types/mintplayer-ng-spark-query-list.d.ts +8 -23
  72. package/types/mintplayer-ng-spark-score-renderers.d.ts +340 -0
  73. package/types/mintplayer-ng-spark-services.d.ts +146 -6
  74. package/types/mintplayer-ng-spark-share-page.d.ts +67 -0
  75. package/types/mintplayer-ng-spark-share.d.ts +26 -0
@@ -1 +1 @@
1
- {"version":3,"file":"mintplayer-ng-spark-query-list.mjs","names":[],"sources":["../../query-list/src/spark-query-list.component.ts","../../query-list/src/spark-query-list.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, DestroyRef, computed, effect, inject, input, output, signal, viewChild, TemplateRef } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { Subscription } from 'rxjs';\nimport { CommonModule, NgTemplateOutlet } from '@angular/common';\nimport { ActivatedRoute, ParamMap, Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { SparkService, SparkStreamingService, SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport { TranslateKeyPipe, ResolveTranslationPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkQueryGridComponent, SparkQueryToolbarAction, SparkSearchBoxComponent, sparkActionClass } from '@mintplayer/ng-spark/grid';\nimport {\n CustomActionDefinition,\n StreamingMessage,\n QueryColumn,\n QueryColumnFilter,\n QueryResultItem,\n SparkDeletedFilter,\n isDateDataType,\n parseWireDate,\n type SparkSelectionModeSetting,\n} from '@mintplayer/ng-spark/models';\nimport { NgComponentOutlet } from '@angular/common';\nimport {\n SPARK_QUERY_LIST_ACTIONS,\n SparkQueryListContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\n\n/**\n * The routed query page: chrome around one {@link SparkQueryGridComponent}.\n *\n * It owns what is genuinely page-shaped and route-shaped, and nothing else:\n *\n * - **Route resolution.** It has no `queryId` input; it reads `paramMap`, and it serves two\n * routes — `query/:queryId`, and `po/:type`, which resolves an entity type to a query. That\n * second one is type-to-query resolution, not query rendering, and is why this component still\n * exists rather than the router pointing at the grid.\n * - **Streaming.** The websocket lives here so it stays out of every PO detail page's bundle;\n * the snapshot is filtered and sorted client-side and handed to the grid as `[data]`.\n * - The action bar, the caption, the LIVE badge, the search box and the New button.\n *\n * The grid itself — columns, cells, paging, the row link, selection, custom-action execution —\n * is the shared component. This page previously wrote out `<bs-datatable>` twice, once per\n * transport, with a shared row template between them; both are gone.\n */\n@Component({\n selector: 'spark-query-list',\n imports: [BsBadgeComponent, CommonModule, NgTemplateOutlet, NgComponentOutlet, BsAlertComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryGridComponent, SparkSearchBoxComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-query-list.component.html',\n styleUrl: './spark-query-list.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.virtual-scrolling]': 'isVirtualScrolling()'\n }\n})\nexport class SparkQueryListComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n private readonly streamingService = inject(SparkStreamingService);\n private readonly destroyRef = inject(DestroyRef);\n protected readonly lang = inject(SparkLanguageService);\n\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n showCustomActions = input(true);\n\n /**\n * Overrides the query's own `selectionMode` (#467, R3), as the sub-query card's input does: for a\n * host that embeds this page and wants, say, `'none'` on a list the actions would make selectable.\n */\n selectionMode = input<SparkSelectionModeSetting | null>(null);\n\n /**\n * Forwarded to the grid, so a query PAGE can replace its row links — the same reason the card\n * forwards it. Without this the escape hatch existed only for a directly-embedded grid.\n */\n rowRoute = input<((row: QueryResultItem) => unknown[] | null) | null>(null);\n\n rowClicked = output<QueryResultItem>();\n createClicked = output<void>();\n customActionExecuted = output<{ action: CustomActionDefinition }>();\n\n colors = Color;\n\n /** The query the grid should render, resolved from the route. Null until it is known. */\n queryId = signal<string | null>(null);\n errorMessage = signal<string | null>(null);\n searchTerm = signal('');\n\n private readonly grid = viewChild(SparkQueryGridComponent);\n\n /**\n * Grid state, surfaced for this page's chrome.\n *\n * Optional `viewChild`, read defensively: the action bar and caption render above the grid, so\n * on the first change-detection pass the query has not resolved yet.\n */\n protected readonly query = computed(() => this.grid()?.query() ?? null);\n protected readonly entityType = computed(() => this.grid()?.entityType() ?? null);\n protected readonly customActions = computed(() => this.grid()?.customActions() ?? []);\n protected readonly canCreate = computed(() => this.grid()?.offersCreate() ?? false);\n protected readonly resultCount = computed(() => this.grid()?.resultCount() ?? null);\n protected readonly isVirtualScrolling = computed(() => this.grid()?.isVirtualScrolling() ?? false);\n protected readonly gridError = computed(() => this.grid()?.errorMessage() ?? null);\n protected readonly permissions = computed(() => this.grid()?.permissions() ?? null);\n\n /** Add-on buttons for the action bar (#460, `SPARK_QUERY_LIST_ACTIONS`), e.g. the Deleted toggle. */\n protected readonly listActions = orderSparkExtensions(inject(SPARK_QUERY_LIST_ACTIONS, { optional: true }), a => a.priority ?? 60);\n\n /**\n * The soft-deletion mode (#460, T2), read from the route's `?deleted=` so the recycle bin survives\n * a reload and the back button from a row opened in it. `exclude` unless the route says otherwise.\n */\n protected readonly deletedMode = signal<SparkDeletedFilter>('exclude');\n\n /** Bumped by `context.reload()`; the grid treats any new value as \"re-run the query\". */\n private readonly reloadToken = signal(0);\n\n protected readonly listContext = computed((): SparkQueryListContext | null => {\n const query = this.query();\n if (!query) return null;\n return {\n query,\n entityType: this.entityType(),\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n setDeleted: (mode: SparkDeletedFilter) => this.setDeleted(mode),\n reload: () => this.reloadToken.update(n => n + 1),\n };\n });\n\n protected readonly gridReloadToken = this.reloadToken.asReadonly();\n\n private setDeleted(mode: SparkDeletedFilter): void {\n this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { deleted: mode === 'exclude' ? null : mode },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n }\n\n /** Whether an action's selection rule is satisfied. Delegated: the grid holds the selection. */\n /** @see SparkPoDetailComponent.customActionClass — same allow-list, same default. */\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /** The grid's toolbar model (#460, M15), the same list the sub-query card renders. */\n protected readonly toolbarActions = computed(() => this.grid()?.toolbarActions() ?? []);\n\n protected toolbarActionClass(action: SparkQueryToolbarAction): string {\n // New keeps its solid primary look; the rest follow their variant.\n return action.kind === 'new' ? 'btn btn-primary' : sparkActionClass(action.definition);\n }\n\n protected isToolbarActionEnabled(action: SparkQueryToolbarAction): boolean {\n return this.grid()?.isToolbarActionEnabled(action) ?? false;\n }\n\n /** The tooltip of an action disabled for want of its client method (`requiresClient`); null otherwise. */\n protected toolbarActionUnavailableReason(action: SparkQueryToolbarAction): string | null {\n return this.grid()?.actionUnavailableReason(action.definition) ?? null;\n }\n\n protected runToolbarAction(action: SparkQueryToolbarAction): void {\n if (action.kind === 'new') {\n // Through this page's own onCreate, so `createClicked` fires exactly as it always has.\n this.onCreate();\n return;\n }\n void this.grid()?.runToolbarAction(action);\n }\n\n protected isActionEnabled(action: CustomActionDefinition): boolean {\n return this.grid()?.isActionEnabled(action) ?? false;\n }\n\n // --- streaming ---------------------------------------------------------------\n\n isStreaming = signal(false);\n private streamingSub: Subscription | null = null;\n /** Columns as sent with the stream's snapshot; empty until it arrives. */\n protected readonly streamColumns = signal<QueryColumn[]>([]);\n private readonly allItems = signal<QueryResultItem[]>([]);\n private readonly streamItems = signal<QueryResultItem[]>([]);\n\n /**\n * Rows handed to the grid, or `null` to let it fetch for itself.\n *\n * Null for a normal query — an empty array would read as \"here are no rows\" and suppress the\n * fetch entirely.\n */\n protected readonly gridData = computed(() =>\n this.query()?.isStreamingQuery ? this.streamItems() : null);\n\n constructor() {\n this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => {\n // The handler is async and this is a subscribe, so a rejection lands nowhere: the metadata\n // load would reject, the query would stay null, and the template would render a spinner\n // FOREVER — while this component has had an errorMessage surface all along that only the\n // fetch path ever reached.\n this.onParamsChange(params).catch((e: unknown) => this.reportLoadFailure(e as HttpErrorResponse));\n });\n\n // Separate from paramMap: switching the recycle bin on changes only the query string, and must\n // not re-resolve the query (which would reset page, sort and filters for nothing).\n this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe(query => {\n this.deletedMode.set(parseSparkDeletedParam(query.get('deleted')) ?? 'exclude');\n this.applyUrlScope(query);\n });\n\n this.destroyRef.onDestroy(() => this.disconnectStreaming());\n\n // Connect the socket once the grid has resolved a streaming query, and disconnect whenever it\n // resolves anything else. The grid knows not to fetch for a streaming query, so there is no\n // window in which both transports are live.\n effect(() => {\n const q = this.query();\n if (q?.isStreamingQuery) {\n this.connectStreaming(q.id);\n } else {\n this.disconnectStreaming();\n }\n });\n\n // Client-side filter and sort for the streaming snapshot. The server never sees these: there\n // is no request to attach them to.\n effect(() => {\n this.searchTerm();\n this.grid()?.settings();\n this.allItems();\n if (this.isStreaming()) this.applyFilter();\n });\n }\n\n // --- URL scope: parent and column filters ----------------------------------------------------\n\n /**\n * The parent the URL scopes the query to (`?parentId=…&parentType=…`), sent exactly as a sub-query\n * sends its container. Both or neither: one without the other is ignored, as the server does.\n */\n protected readonly urlParentId = signal('');\n protected readonly urlParentType = signal('');\n\n /**\n * Column filters from the URL: every other query-string parameter, `?Language=en&Script=Latn`\n * (repeat a name for several values). The grid applies only those naming an attribute of the\n * query's type; the rest are ignored. This is the contributions History link's shape, and works for\n * any query.\n */\n protected readonly urlFilters = signal<QueryColumnFilter[]>([]);\n\n /** The URL filters the grid applied, as removable chips. */\n protected readonly activeUrlFilters = computed(() => this.grid()?.appliedPresetFilters() ?? []);\n\n /** Query-string names the page or the row links own, never column filters. */\n private static readonly reservedParams = new Set(['deleted', 'parentId', 'parentType', 'queryId', 'parentDeleted']);\n\n private applyUrlScope(query: ParamMap): void {\n const parentId = query.get('parentId') ?? '';\n const parentType = query.get('parentType') ?? '';\n const both = !!parentId && !!parentType;\n if (this.urlParentId() !== (both ? parentId : '')) this.urlParentId.set(both ? parentId : '');\n if (this.urlParentType() !== (both ? parentType : '')) this.urlParentType.set(both ? parentType : '');\n\n const filters: QueryColumnFilter[] = query.keys\n .filter(key => !SparkQueryListComponent.reservedParams.has(key))\n .map(key => ({ name: key, includes: query.getAll(key).filter(v => v !== '') }))\n .filter(f => f.includes.length > 0);\n // Only on a real change: a new array would refetch the grid for nothing (the Deleted toggle\n // also changes the query string).\n if (JSON.stringify(filters) !== JSON.stringify(this.urlFilters())) this.urlFilters.set(filters);\n }\n\n /** Drops one URL filter (its query-string parameter); the grid refetches. */\n protected clearUrlFilter(name: string): void {\n this.router.navigate([], { relativeTo: this.route, queryParams: { [name]: null }, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n /** Drops the parent and every URL filter. */\n protected clearUrlScope(): void {\n const queryParams: Record<string, null> = { parentId: null, parentType: null };\n for (const filter of this.urlFilters()) queryParams[filter.name] = null;\n this.router.navigate([], { relativeTo: this.route, queryParams, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n protected filterValues(filter: QueryColumnFilter): string {\n return (filter.includes ?? []).map(v => String(v)).join(', ');\n }\n\n /** The column's label for a chip: the query's own column label, else the attribute's, else its name. */\n protected filterLabel(name: string): string {\n const attribute = this.entityType()?.attributes?.find(a => a.name === name);\n return this.lang.resolve(attribute?.label) || name;\n }\n\n private async onParamsChange(params: ParamMap): Promise<void> {\n this.errorMessage.set(null);\n this.queryId.set(null);\n this.allItems.set([]);\n this.streamItems.set([]);\n this.streamColumns.set([]);\n this.disconnectStreaming();\n\n const queryId = params.get('queryId');\n const typeParam = params.get('type');\n\n if (queryId) {\n this.queryId.set(queryId);\n return;\n }\n\n if (!typeParam) return;\n\n // `po/:type` — the route names an entity type, so find the query that lists it. The grid takes\n // a query, and this translation is the only reason it cannot take the route directly.\n const [entityTypes, queries] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.sparkService.getQueries(),\n ]);\n const entityType = entityTypes.find(t => t.id === typeParam || t.alias === typeParam);\n if (!entityType) {\n this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n return;\n }\n\n const singularName = entityType.name;\n const match = queries.find(q => {\n if (q.entityType === singularName) return true;\n const sourceName = q.source.includes('.') ? q.source.substring(q.source.indexOf('.') + 1) : q.source;\n return sourceName === singularName || sourceName === singularName + 's';\n });\n\n if (match) this.queryId.set(match.alias || match.id);\n else this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n }\n\n /**\n * A load failure has to render, not just be swallowed: a denied query answers 404 (audit M-3, so\n * existence is not leaked), which is indistinguishable from a missing one — hence a deliberately\n * generic message rather than a guess at which it was.\n */\n private reportLoadFailure(err: HttpErrorResponse): void {\n this.errorMessage.set(\n err?.status === 404\n ? (this.lang.t('spark.query.unavailable') || 'This list is not available.')\n : (err?.error?.error || err?.message || 'An unexpected error occurred'));\n }\n\n protected async onCustomAction(action: CustomActionDefinition): Promise<void> {\n await this.grid()?.onCustomAction(action);\n }\n\n protected onCreate(): void {\n this.createClicked.emit();\n const et = this.entityType();\n if (et) this.router.navigate(['/po', et.alias || et.id, 'new']);\n }\n\n protected clearSearch(): void {\n this.searchTerm.set('');\n }\n\n private connectStreaming(queryId: string): void {\n if (this.streamingSub) return;\n this.isStreaming.set(true);\n\n this.streamingSub = this.streamingService.connectToStreamingQuery(queryId).subscribe({\n next: (message) => this.handleStreamingMessage(message),\n error: (err) => {\n this.errorMessage.set(err?.message || 'Streaming connection failed');\n this.isStreaming.set(false);\n },\n complete: () => this.isStreaming.set(false),\n });\n }\n\n private disconnectStreaming(): void {\n if (this.streamingSub) {\n this.streamingSub.unsubscribe();\n this.streamingSub = null;\n }\n this.isStreaming.set(false);\n }\n\n private handleStreamingMessage(message: StreamingMessage): void {\n switch (message.type) {\n case 'snapshot':\n this.errorMessage.set(null);\n // Columns come with the snapshot and never change for the life of the stream, so they are\n // stored once and handed to the grid alongside the rows — a projection cannot describe\n // itself, and the grid has no entity-type metadata to fall back on any more.\n this.streamColumns.set(message.columns);\n this.allItems.set(message.data);\n break;\n\n case 'patch':\n if (message.updated.length > 0) {\n this.allItems.update(items => items.map(item => {\n const patch = message.updated.find(u => u.id === item.id);\n if (!patch) return item;\n return {\n ...item,\n values: item.values.map(v =>\n v.key in patch.values ? { ...v, value: patch.values[v.key] } : v),\n };\n }));\n }\n break;\n\n case 'error':\n this.errorMessage.set(message.message);\n break;\n }\n }\n\n private applyFilter(): void {\n let items = this.allItems();\n\n const term = this.searchTerm().toLowerCase();\n if (term) {\n items = items.filter(item =>\n item.values.some(v => String(v.value ?? '').toLowerCase().includes(term)));\n }\n\n // The datatable in `[data]` mode also sorts on header clicks, but the sort must survive a\n // patch: re-deriving from `allItems` without re-applying it would silently reorder the grid\n // under the user on every update.\n const sortCols = this.grid()?.settings().sortColumns ?? [];\n if (sortCols.length > 0) {\n const dataTypes = new Map(this.streamColumns().map(c => [c.name, c.dataType]));\n items = [...items].sort((a, b) => {\n for (const col of sortCols) {\n const aVal = a.values.find(v => v.key === col.property)?.value;\n const bVal = b.values.find(v => v.key === col.property)?.value;\n const cmp = compareStreamValues(aVal, bVal, dataTypes.get(col.property));\n if (cmp !== 0) return col.direction === 'descending' ? -cmp : cmp;\n }\n return 0;\n });\n }\n\n this.streamItems.set(items);\n }\n}\n\n/**\n * Orders two streamed cell values the way the server orders the same column.\n *\n * The values are what the wire carries: a date is the raw ISO string WITH its own offset and with\n * trailing fraction zeros trimmed (`…T09:00:00-05:00`, `…T09:00:00.5-05:00`), a number is a number.\n * Comparing them as text sorted dates by wall clock and `10` before `9`; a date compares by instant\n * and a number numerically (docs/datetimeoffset_query_sort_filter_PRD.md, F4).\n *\n * A missing value sorts first ascending, as the empty string always did.\n */\nfunction compareStreamValues(a: unknown, b: unknown, dataType: string | undefined): number {\n const aMissing = a === null || a === undefined || a === '';\n const bMissing = b === null || b === undefined || b === '';\n if (aMissing || bMissing) return aMissing === bMissing ? 0 : aMissing ? -1 : 1;\n\n if (isDateDataType(dataType)) {\n const aDate = parseWireDate(a);\n const bDate = parseWireDate(b);\n if (aDate && bDate) return aDate.getTime() - bDate.getTime();\n } else if (dataType === 'number' || dataType === 'decimal') {\n const aNumber = Number(a);\n const bNumber = Number(b);\n if (!isNaN(aNumber) && !isNaN(bNumber)) return aNumber - bNumber;\n }\n\n return String(a).localeCompare(String(b));\n}\n","<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rules. -->\n @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | 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_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; 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\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isStreaming()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid, both transports. `data` is null for a normal query, so the grid fetches and pages\n server-side; for a streaming query this page owns the socket and hands the filtered snapshot\n in. This used to be two near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [data]=\"gridData()\"\n [columns]=\"gridData() ? streamColumns() : null\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"mintplayer-ng-spark-query-list.mjs","names":[],"sources":["../../query-list/src/spark-query-list.component.ts","../../query-list/src/spark-query-list.component.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, input, output, signal, viewChild, TemplateRef } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule, NgTemplateOutlet } from '@angular/common';\nimport { ActivatedRoute, ParamMap, Router } from '@angular/router';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsGridComponent, BsGridRowDirective, BsGridColumnDirective } from '@mintplayer/ng-bootstrap/grid';\nimport { BsPriorityNavComponent, BsPriorityNavItemDirective } from '@mintplayer/ng-bootstrap/priority-nav';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { SparkService, SparkLanguageService } from '@mintplayer/ng-spark/services';\nimport { TranslateKeyPipe, ResolveTranslationPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkQueryGridComponent, SparkQueryToolbarAction, SparkSearchBoxComponent, sparkActionClass } from '@mintplayer/ng-spark/grid';\nimport {\n CustomActionDefinition,\n QueryColumnFilter,\n QueryResultItem,\n SparkDeletedFilter,\n type SparkSelectionModeSetting,\n} from '@mintplayer/ng-spark/models';\nimport { NgComponentOutlet } from '@angular/common';\nimport {\n SPARK_QUERY_LIST_ACTIONS,\n SparkQueryListContext,\n orderSparkExtensions,\n parseSparkDeletedParam,\n} from '@mintplayer/ng-spark/panels';\n\n/**\n * The routed query page: chrome around one {@link SparkQueryGridComponent}.\n *\n * It owns what is genuinely page-shaped and route-shaped, and nothing else:\n *\n * - **Route resolution.** It has no `queryId` input; it reads `paramMap`, and it serves two\n * routes — `query/:queryId`, and `po/:type`, which resolves an entity type to a query. That\n * second one is type-to-query resolution, not query rendering, and is why this component still\n * exists rather than the router pointing at the grid.\n * - The action bar, the caption, the LIVE badge (the grid's `isLive()`), the search box and the\n * New button.\n *\n * Live updates are the grid's (X4): a live query is followed by `spark-query-grid` itself, with\n * server-side paging, sort and search, so every embedded grid follows it too — not only this page.\n *\n * The grid itself — columns, cells, paging, the row link, selection, custom-action execution —\n * is the shared component. This page previously wrote out `<bs-datatable>` twice, once per\n * transport, with a shared row template between them; both are gone.\n */\n@Component({\n selector: 'spark-query-list',\n imports: [BsBadgeComponent, CommonModule, NgTemplateOutlet, NgComponentOutlet, BsAlertComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsPriorityNavComponent, BsPriorityNavItemDirective, BsSpinnerComponent, SparkIconComponent, SparkQueryGridComponent, SparkSearchBoxComponent, ResolveTranslationPipe, TranslateKeyPipe],\n templateUrl: './spark-query-list.component.html',\n styleUrl: './spark-query-list.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.virtual-scrolling]': 'isVirtualScrolling()'\n }\n})\nexport class SparkQueryListComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly sparkService = inject(SparkService);\n protected readonly lang = inject(SparkLanguageService);\n\n extraActionsTemplate = input<TemplateRef<void> | null>(null);\n showCustomActions = input(true);\n\n /**\n * Overrides the query's own `selectionMode` (#467, R3), as the sub-query card's input does: for a\n * host that embeds this page and wants, say, `'none'` on a list the actions would make selectable.\n */\n selectionMode = input<SparkSelectionModeSetting | null>(null);\n\n /**\n * Forwarded to the grid, so a query PAGE can replace its row links — the same reason the card\n * forwards it. Without this the escape hatch existed only for a directly-embedded grid.\n */\n rowRoute = input<((row: QueryResultItem) => unknown[] | null) | null>(null);\n\n rowClicked = output<QueryResultItem>();\n createClicked = output<void>();\n customActionExecuted = output<{ action: CustomActionDefinition }>();\n\n colors = Color;\n\n /** The query the grid should render, resolved from the route. Null until it is known. */\n queryId = signal<string | null>(null);\n errorMessage = signal<string | null>(null);\n searchTerm = signal('');\n\n private readonly grid = viewChild(SparkQueryGridComponent);\n\n /**\n * Grid state, surfaced for this page's chrome.\n *\n * Optional `viewChild`, read defensively: the action bar and caption render above the grid, so\n * on the first change-detection pass the query has not resolved yet.\n */\n protected readonly query = computed(() => this.grid()?.query() ?? null);\n protected readonly entityType = computed(() => this.grid()?.entityType() ?? null);\n protected readonly customActions = computed(() => this.grid()?.customActions() ?? []);\n protected readonly canCreate = computed(() => this.grid()?.offersCreate() ?? false);\n protected readonly resultCount = computed(() => this.grid()?.resultCount() ?? null);\n protected readonly isVirtualScrolling = computed(() => this.grid()?.isVirtualScrolling() ?? false);\n protected readonly gridError = computed(() => this.grid()?.errorMessage() ?? null);\n /** Whether the grid follows the query over `/spark/live` right now: the LIVE badge. */\n protected readonly isLive = computed(() => this.grid()?.isLive() ?? false);\n protected readonly permissions = computed(() => this.grid()?.permissions() ?? null);\n\n /** Add-on buttons for the action bar (#460, `SPARK_QUERY_LIST_ACTIONS`), e.g. the Deleted toggle. */\n protected readonly listActions = orderSparkExtensions(inject(SPARK_QUERY_LIST_ACTIONS, { optional: true }), a => a.priority ?? 60);\n\n /**\n * The soft-deletion mode (#460, T2), read from the route's `?deleted=` so the recycle bin survives\n * a reload and the back button from a row opened in it. `exclude` unless the route says otherwise.\n */\n protected readonly deletedMode = signal<SparkDeletedFilter>('exclude');\n\n /** Bumped by `context.reload()`; the grid treats any new value as \"re-run the query\". */\n private readonly reloadToken = signal(0);\n\n protected readonly listContext = computed((): SparkQueryListContext | null => {\n const query = this.query();\n if (!query) return null;\n return {\n query,\n entityType: this.entityType(),\n permissions: this.permissions(),\n deleted: this.deletedMode(),\n setDeleted: (mode: SparkDeletedFilter) => this.setDeleted(mode),\n reload: () => this.reloadToken.update(n => n + 1),\n };\n });\n\n protected readonly gridReloadToken = this.reloadToken.asReadonly();\n\n private setDeleted(mode: SparkDeletedFilter): void {\n this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { deleted: mode === 'exclude' ? null : mode },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n }\n\n /** Whether an action's selection rule is satisfied. Delegated: the grid holds the selection. */\n /** @see SparkPoDetailComponent.customActionClass — same allow-list, same default. */\n protected customActionClass(action: CustomActionDefinition): string {\n const variant = action.variant?.toLowerCase();\n switch (variant) {\n case 'danger':\n case 'warning':\n case 'primary':\n case 'secondary':\n case 'success':\n return `btn btn-${variant}`;\n default:\n return 'btn btn-outline-primary';\n }\n }\n\n /** The grid's toolbar model (#460, M15), the same list the sub-query card renders. */\n protected readonly toolbarActions = computed(() => this.grid()?.toolbarActions() ?? []);\n\n protected toolbarActionClass(action: SparkQueryToolbarAction): string {\n // New keeps its solid primary look; the rest follow their variant.\n return action.kind === 'new' ? 'btn btn-primary' : sparkActionClass(action.definition);\n }\n\n protected isToolbarActionEnabled(action: SparkQueryToolbarAction): boolean {\n return this.grid()?.isToolbarActionEnabled(action) ?? false;\n }\n\n /** The tooltip of an action disabled for want of its client method (`requiresClient`); null otherwise. */\n protected toolbarActionUnavailableReason(action: SparkQueryToolbarAction): string | null {\n return this.grid()?.actionUnavailableReason(action.definition) ?? null;\n }\n\n protected runToolbarAction(action: SparkQueryToolbarAction): void {\n if (action.kind === 'new') {\n // Through this page's own onCreate, so `createClicked` fires exactly as it always has.\n this.onCreate();\n return;\n }\n void this.grid()?.runToolbarAction(action);\n }\n\n protected isActionEnabled(action: CustomActionDefinition): boolean {\n return this.grid()?.isActionEnabled(action) ?? false;\n }\n\n constructor() {\n this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => {\n // The handler is async and this is a subscribe, so a rejection lands nowhere: the metadata\n // load would reject, the query would stay null, and the template would render a spinner\n // FOREVER — while this component has had an errorMessage surface all along that only the\n // fetch path ever reached.\n this.onParamsChange(params).catch((e: unknown) => this.reportLoadFailure(e as HttpErrorResponse));\n });\n\n // Separate from paramMap: switching the recycle bin on changes only the query string, and must\n // not re-resolve the query (which would reset page, sort and filters for nothing).\n this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe(query => {\n this.deletedMode.set(parseSparkDeletedParam(query.get('deleted')) ?? 'exclude');\n this.applyUrlScope(query);\n });\n }\n\n // --- URL scope: parent and column filters ----------------------------------------------------\n\n /**\n * The parent the URL scopes the query to (`?parentId=…&parentType=…`), sent exactly as a sub-query\n * sends its container. Both or neither: one without the other is ignored, as the server does.\n */\n protected readonly urlParentId = signal('');\n protected readonly urlParentType = signal('');\n\n /**\n * Column filters from the URL: every other query-string parameter, `?Language=en&Script=Latn`\n * (repeat a name for several values). The grid applies only those naming an attribute of the\n * query's type; the rest are ignored. This is the contributions History link's shape, and works for\n * any query.\n */\n protected readonly urlFilters = signal<QueryColumnFilter[]>([]);\n\n /** The URL filters the grid applied, as removable chips. */\n protected readonly activeUrlFilters = computed(() => this.grid()?.appliedPresetFilters() ?? []);\n\n /** Query-string names the page or the row links own, never column filters. */\n private static readonly reservedParams = new Set(['deleted', 'parentId', 'parentType', 'queryId', 'parentDeleted']);\n\n private applyUrlScope(query: ParamMap): void {\n const parentId = query.get('parentId') ?? '';\n const parentType = query.get('parentType') ?? '';\n const both = !!parentId && !!parentType;\n if (this.urlParentId() !== (both ? parentId : '')) this.urlParentId.set(both ? parentId : '');\n if (this.urlParentType() !== (both ? parentType : '')) this.urlParentType.set(both ? parentType : '');\n\n const filters: QueryColumnFilter[] = query.keys\n .filter(key => !SparkQueryListComponent.reservedParams.has(key))\n .map(key => ({ name: key, includes: query.getAll(key).filter(v => v !== '') }))\n .filter(f => f.includes.length > 0);\n // Only on a real change: a new array would refetch the grid for nothing (the Deleted toggle\n // also changes the query string).\n if (JSON.stringify(filters) !== JSON.stringify(this.urlFilters())) this.urlFilters.set(filters);\n }\n\n /** Drops one URL filter (its query-string parameter); the grid refetches. */\n protected clearUrlFilter(name: string): void {\n this.router.navigate([], { relativeTo: this.route, queryParams: { [name]: null }, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n /** Drops the parent and every URL filter. */\n protected clearUrlScope(): void {\n const queryParams: Record<string, null> = { parentId: null, parentType: null };\n for (const filter of this.urlFilters()) queryParams[filter.name] = null;\n this.router.navigate([], { relativeTo: this.route, queryParams, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n protected filterValues(filter: QueryColumnFilter): string {\n return (filter.includes ?? []).map(v => String(v)).join(', ');\n }\n\n /** The column's label for a chip: the query's own column label, else the attribute's, else its name. */\n protected filterLabel(name: string): string {\n const attribute = this.entityType()?.attributes?.find(a => a.name === name);\n return this.lang.resolve(attribute?.label) || name;\n }\n\n private async onParamsChange(params: ParamMap): Promise<void> {\n this.errorMessage.set(null);\n this.queryId.set(null);\n\n const queryId = params.get('queryId');\n const typeParam = params.get('type');\n\n if (queryId) {\n this.queryId.set(queryId);\n return;\n }\n\n if (!typeParam) return;\n\n // `po/:type` — the route names an entity type, so find the query that lists it. The grid takes\n // a query, and this translation is the only reason it cannot take the route directly.\n const [entityTypes, queries] = await Promise.all([\n this.sparkService.getEntityTypes(),\n this.sparkService.getQueries(),\n ]);\n const entityType = entityTypes.find(t => t.id === typeParam || t.alias === typeParam);\n if (!entityType) {\n this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n return;\n }\n\n const singularName = entityType.name;\n const match = queries.find(q => {\n if (q.entityType === singularName) return true;\n const sourceName = q.source.includes('.') ? q.source.substring(q.source.indexOf('.') + 1) : q.source;\n return sourceName === singularName || sourceName === singularName + 's';\n });\n\n if (match) this.queryId.set(match.alias || match.id);\n else this.reportLoadFailure({ status: 404 } as HttpErrorResponse);\n }\n\n /**\n * A load failure has to render, not just be swallowed: a denied query answers 404 (audit M-3, so\n * existence is not leaked), which is indistinguishable from a missing one — hence a deliberately\n * generic message rather than a guess at which it was.\n */\n private reportLoadFailure(err: HttpErrorResponse): void {\n this.errorMessage.set(\n err?.status === 404\n ? this.lang.t('spark.query.unavailable', 'This list is not available.')\n : (err?.error?.error || err?.message || 'An unexpected error occurred'));\n }\n\n protected async onCustomAction(action: CustomActionDefinition): Promise<void> {\n await this.grid()?.onCustomAction(action);\n }\n\n protected onCreate(): void {\n this.createClicked.emit();\n const et = this.entityType();\n if (et) this.router.navigate(['/po', et.alias || et.id, 'new']);\n }\n\n protected clearSearch(): void {\n this.searchTerm.set('');\n }\n}\n","<div class=\"d-flex flex-column h-100\">\n <div class=\"spark-actionbar py-2 flex-shrink-0\" [class.spark-actionbar-wide]=\"isVirtualScrolling()\">\n <bs-priority-nav [moreLabel]=\"lang.t('common.more')\" [collapseAt]=\"'sm'\">\n <!-- The grid's toolbar model (#460 M15, #467 R3): New, Edit, Delete and the custom actions —\n the list the sub-query card renders too, labels and icons from the action catalogue.\n Disabled, not hidden: hiding makes the affordance undiscoverable and reflows the\n priority nav on every selection change. The server enforces the same rules. -->\n @for (action of toolbarActions(); track action.name) {\n @if (action.kind !== 'custom' || showCustomActions()) {\n <button *bsPriorityNavItem=\"action.priority\" type=\"button\" [class]=\"toolbarActionClass(action)\"\n [attr.data-action]=\"action.name\"\n [disabled]=\"!isToolbarActionEnabled(action)\" [attr.title]=\"toolbarActionUnavailableReason(action)\" (click)=\"runToolbarAction(action)\">\n @if (action.definition.icon && action.kind !== 'custom') {\n <spark-icon [name]=\"action.definition.icon\" />\n }\n {{ action.definition.label | 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_QUERY_LIST_ACTIONS), e.g. the soft-delete Deleted toggle. -->\n @if (listContext(); as ctx) {\n @for (action of listActions; 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\n <h2 class=\"mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n {{ (query()?.label | resolveTranslation) || query()?.name || ('common.loading' | t) }}\n @if (isLive()) {\n <bs-badge [type]=\"colors.success\" class=\"ms-2 align-middle spark-live-badge\">LIVE</bs-badge>\n }\n @if (deletedMode() === 'only') {\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 align-middle spark-deleted-badge\">{{ 'softDelete.deleted' | t }}</bs-badge>\n }\n </h2>\n\n @if (errorMessage(); as err) {\n <!-- The route could not be resolved to a query at all. A failure INSIDE the grid renders in\n the grid; this is the one it cannot report, because it never got a query id. -->\n <bs-alert [type]=\"colors.danger\" class=\"m-3 d-block\">\n {{ err }}\n </bs-alert>\n } @else if (queryId(); as qId) {\n <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"12\">\n <!-- The same box as the sub-query card's header (#460 M15): debounce, clear button. -->\n <spark-search-box [(term)]=\"searchTerm\" />\n </div>\n <!-- Its own full-width row rather than a column beside the box: the count is\n occasional, and reserving 8 columns for it shrank the search permanently. The\n @if moved out of the column so nothing reserves vertical space when absent. -->\n @if (searchTerm() && resultCount() !== null) {\n <div [md]=\"12\" class=\"text-end mt-1\">\n <span class=\"text-muted\">\n {{ resultCount() }} {{ resultCount() === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n </div>\n }\n </div>\n </bs-grid>\n </div>\n\n <!--\n One grid. It fetches and pages server-side, over HTTP or, for a live query, over /spark/live\n (the grid owns that, so embedded grids follow live queries too). This used to be two\n near-identical <bs-datatable> blocks with a shared row template.\n -->\n <!-- The URL's scope (#contributions M5b): a parent and column filters from the query string, as\n the History link sends them. Shown so the narrowing is visible, and clearable. -->\n @if (urlParentId() || activeUrlFilters().length) {\n <div class=\"spark-url-filters d-flex flex-wrap align-items-center gap-2 mb-2 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <span class=\"text-muted small\">{{ 'common.filteredBy' | t }}</span>\n @for (filter of activeUrlFilters(); track filter.name) {\n <bs-badge [type]=\"colors.secondary\" class=\"spark-url-filter\" [attr.data-filter]=\"filter.name\">\n {{ filterLabel(filter.name) }}: {{ filterValues(filter) }}\n <button type=\"button\" class=\"spark-url-filter-clear\" [attr.aria-label]=\"'common.clearFilter' | t\"\n (click)=\"clearUrlFilter(filter.name)\">\n <spark-icon name=\"x-circle\" />\n </button>\n </bs-badge>\n }\n <button type=\"button\" class=\"btn btn-sm btn-link spark-url-filters-clear\" (click)=\"clearUrlScope()\">\n {{ 'common.clearFilters' | t }}\n </button>\n </div>\n }\n\n <spark-query-grid class=\"flex-grow-1\"\n [queryId]=\"qId\"\n [parentId]=\"urlParentId()\"\n [parentType]=\"urlParentType()\"\n [presetFilters]=\"urlFilters()\"\n [search]=\"searchTerm()\"\n [deleted]=\"deletedMode()\"\n [reloadToken]=\"gridReloadToken()\"\n [rowRoute]=\"rowRoute()\"\n [selectionModeSetting]=\"selectionMode()\"\n (rowClicked)=\"rowClicked.emit($event)\"\n (customActionExecuted)=\"customActionExecuted.emit($event)\" />\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n"],"mappings":"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"}
@@ -31,7 +31,7 @@ var SparkParagraphRendererComponent = class SparkParagraphRendererComponent {
31
31
  }, ...ngDevMode ? [{ debugName: "paragraphs" }] : /* istanbul ignore next */ []);
32
32
  static ɵfac = i0.ɵɵngDeclareFactory({
33
33
  minVersion: "12.0.0",
34
- version: "22.2.0",
34
+ version: "22.2.2",
35
35
  ngImport: i0,
36
36
  type: SparkParagraphRendererComponent,
37
37
  deps: [],
@@ -39,7 +39,7 @@ var SparkParagraphRendererComponent = class SparkParagraphRendererComponent {
39
39
  });
40
40
  static ɵcmp = i0.ɵɵngDeclareComponent({
41
41
  minVersion: "17.0.0",
42
- version: "22.2.0",
42
+ version: "22.2.2",
43
43
  type: SparkParagraphRendererComponent,
44
44
  isStandalone: true,
45
45
  selector: "spark-paragraph-renderer",
@@ -76,7 +76,7 @@ var SparkParagraphRendererComponent = class SparkParagraphRendererComponent {
76
76
  };
77
77
  i0.ɵɵngDeclareClassMetadata({
78
78
  minVersion: "12.0.0",
79
- version: "22.2.0",
79
+ version: "22.2.2",
80
80
  ngImport: i0,
81
81
  type: SparkParagraphRendererComponent,
82
82
  decorators: [{
@@ -60,7 +60,7 @@ var SparkRetryActionModalComponent = class SparkRetryActionModalComponent {
60
60
  }
61
61
  static ɵfac = i0.ɵɵngDeclareFactory({
62
62
  minVersion: "12.0.0",
63
- version: "22.2.0",
63
+ version: "22.2.2",
64
64
  ngImport: i0,
65
65
  type: SparkRetryActionModalComponent,
66
66
  deps: [],
@@ -68,7 +68,7 @@ var SparkRetryActionModalComponent = class SparkRetryActionModalComponent {
68
68
  });
69
69
  static ɵcmp = i0.ɵɵngDeclareComponent({
70
70
  minVersion: "17.0.0",
71
- version: "22.2.0",
71
+ version: "22.2.2",
72
72
  type: SparkRetryActionModalComponent,
73
73
  isStandalone: true,
74
74
  selector: "spark-retry-action-modal",
@@ -197,7 +197,7 @@ var SparkRetryActionModalComponent = class SparkRetryActionModalComponent {
197
197
  };
198
198
  i0.ɵɵngDeclareClassMetadata({
199
199
  minVersion: "12.0.0",
200
- version: "22.2.0",
200
+ version: "22.2.2",
201
201
  ngImport: i0,
202
202
  type: SparkRetryActionModalComponent,
203
203
  decorators: [{