Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions projects/demo/src/app/pages/table/table-demo.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
[enableRowSelection]="_tableDemoService.model.enableRowSelection"
[enableSorting]="_tableDemoService.model.enableSorting"
[masterDetail]="_tableDemoService.model.masterDetail"
[masterDetailToggleColumnId]="_tableDemoService.model.masterDetailToggleColumnId"
[masterDetailTogglePosition]="_tableDemoService.model.masterDetailTogglePosition"
[masterDetailTriggerMode]="_tableDemoService.model.masterDetailTriggerMode"
[masterDetailTriggerTemplateName]="_tableDemoService.model.masterDetailTriggerTemplateName"
[showDetailHeader]="_tableDemoService.model.showDetailHeader"
[detailStickyColumns]="_tableDemoService.model.detailStickyColumns"
[hasDetailRow]="_hasDetailRow"
[hasDetailRow]="_hasDetailRow()"
[isRowSelectable]="_isRowSelectable"
[withBorder]="_tableDemoService.model.withBorder"
(sortChange)="_dataSource.sort($event)"
Expand All @@ -48,6 +52,16 @@
value | translate
}}</span>
<span *idsCellTemplate="'customDetailTemplate'; let row" class="ids-table-cell-template-detail">{{ row?.summary }}</span>
<ng-template let-detail="detail" let-value="cellValue" idsCellTemplate="detailTrigger">
<div class="flex items-center gap-2">
<span>{{ value }}</span>
@if (detail.hasDetailRow) {
<button idsButton type="button" [idsTableDetailTrigger]="detail">
{{ (detail.expanded ? "TABLE.DETAIL_TRIGGERS.CLOSE" : "TABLE.DETAIL_TRIGGERS.OPEN") | translate }}
</button>
}
</div>
</ng-template>
</ids-table>
<ids-paginator [length]="_totalRows()" [(pageIndex)]="_currentPageIndex" (pageChanged)="_dataSource.page($event)" />
</div>
Expand All @@ -60,7 +74,7 @@
controls
controlName="Table"
configType="input"
[controlConfig]="_tableDemoService.inputControlConfig"
[controlConfig]="_tableDemoService.demoInputControlConfig"
[(model)]="_tableDemoService.model"
(resetted)="_tableDemoService.reset()"
/>
Expand Down
15 changes: 10 additions & 5 deletions projects/demo/src/app/pages/table/table-demo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { DemoAndCodeComponent } from '../../components/tabs/demo-and-code/demo-a
import { TryoutControlComponent } from '../../components/tryout/tryout-controls.component';
import { TryoutComponent } from '../../components/tryout/tryout.component';

import { Component, DestroyRef, inject, Injectable, input, OnInit, viewChild, ViewEncapsulation } from '@angular/core';
import { Component, computed, DestroyRef, inject, Injectable, input, OnInit, viewChild, ViewEncapsulation } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
import { IdsButtonComponent } from '@i-cell/ids-angular/button';
Expand All @@ -17,6 +17,7 @@ import {
IdsTableCellTemplateDirective,
IdsTableColumnDef,
IdsTableComponent,
IdsTableDetailTriggerDirective,
IdsTableIntl,
IdsTableRequestFactory,
IdsTableRowKeydownEvent,
Expand Down Expand Up @@ -79,6 +80,7 @@ export class TableDemoTableIntl extends IdsTableIntl<PeriodicTableElement> {
IdsTableSortDescIconDirective,
IdsTableSortNoneIconDirective,
IdsTableComponent,
IdsTableDetailTriggerDirective,
FormsModule,
TranslateModule,
TryoutComponent,
Expand Down Expand Up @@ -107,6 +109,13 @@ export class TableDemoComponent implements OnInit {
(paginationData, sortInfo) => this._tableDemoService.getPeriodicTable(paginationData, sortInfo);

protected _dataSource = new ServerSideDataSource(this._requestFactory);

protected _hasDetailRow = computed(() => {
const excludedSymbol = this._tableDemoService.excludedDetailSymbol();
return (_index: number, row: PeriodicTableElement): boolean =>
!!row.summary && (!excludedSymbol || row.symbol !== excludedSymbol);
});

protected _totalRows = toSignal<number, number>(
this._dataSource.pageInfo$.pipe(
map((pageInfo) => (pageInfo?.totalRows || 0)),
Expand Down Expand Up @@ -154,10 +163,6 @@ export class TableDemoComponent implements OnInit {
console.info('onContentChanged');
}

protected _hasDetailRow(index: number, rowData: PeriodicTableElement): boolean {
return !!rowData.summary;
}

protected _isRowSelectable(index: number, rowData: PeriodicTableElement): boolean {
return !!rowData.number && rowData.number > 0;
}
Expand Down
78 changes: 75 additions & 3 deletions projects/demo/src/app/pages/table/table-demo.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { environment } from '../../../environments/environment';
import { WidgetDocsService } from '../../services/widget-docs.service';

import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { inject, Injectable, signal } from '@angular/core';
import { DemoApiControlConfig } from '@demo-types/demo-api-control.type';
import { DemoControl, DemoControlConfig } from '@demo-types/demo-control.type';
import { DemoMethodConfig } from '@demo-types/demo-method.type';
Expand All @@ -19,6 +19,10 @@ import {
IdsTableAppearance,
IdsTableAppearanceType,
IdsTableColumnDef,
IdsTableDetailTogglePosition,
IdsTableDetailTogglePositionType,
IdsTableDetailTriggerMode,
IdsTableDetailTriggerModeType,
IdsTableRequestPaginationData,
IdsTableResponseData,
IdsTableSortDirection,
Expand All @@ -44,6 +48,10 @@ export type TableInputControls = {
clearSelectionOnChange: boolean;
enableSorting: boolean;
masterDetail: boolean;
masterDetailToggleColumnId: string | null;
masterDetailTogglePosition: IdsTableDetailTogglePositionType;
masterDetailTriggerMode: IdsTableDetailTriggerModeType;
masterDetailTriggerTemplateName: string;
showDetailHeader: boolean;
detailStickyColumns: boolean;
withBorder: boolean;
Expand Down Expand Up @@ -132,6 +140,7 @@ export class TableDemoService {
},
{
id: 'symbol',
field: 'symbol',
label: 'TABLE.COL.SYMBOL',
headerCellRenderer: 'translate2',
cellRenderer: 'symbol',
Expand Down Expand Up @@ -234,6 +243,42 @@ export class TableDemoService {
default: defaultConfig.masterDetail,
control: DemoControl.SWITCH,
},
masterDetailToggleColumnId: {
description: this._widgetDocs.getDescription(TABLE_DOCS_PATH, 'masterDetailToggleColumnId',
'Visible data column ID for the master-detail toggle.'),
type: 'string | null',
default: defaultConfig.masterDetailToggleColumnId,
control: DemoControl.SELECT,
list: [
'',
...this.colDefs.filter((column) => column.visible !== false).map((column) => column.id),
],
},
masterDetailTogglePosition: {
description: this._widgetDocs.getDescription(TABLE_DOCS_PATH, 'masterDetailTogglePosition', 'Position of the default detail toggle.'),
type: 'IdsTableDetailTogglePositionType',
default: defaultConfig.masterDetailTogglePosition,
control: DemoControl.SELECT,
list: convertEnumToStringArray(IdsTableDetailTogglePosition),
},
masterDetailTriggerMode: {
description: this._widgetDocs.getDescription(TABLE_DOCS_PATH, 'masterDetailTriggerMode', 'Default or custom detail trigger.'),
type: 'IdsTableDetailTriggerModeType',
default: defaultConfig.masterDetailTriggerMode,
control: DemoControl.SELECT,
list: convertEnumToStringArray(IdsTableDetailTriggerMode),
},
masterDetailTriggerTemplateName: {
description: this._widgetDocs.getDescription(TABLE_DOCS_PATH, 'masterDetailTriggerTemplateName',
'Custom detail trigger template name.'),
type: 'string',
default: defaultConfig.masterDetailTriggerTemplateName,
control: DemoControl.SELECT,
list: [
defaultConfig.masterDetailTriggerTemplateName,
'missingTemplate',
],
},
showDetailHeader: {
description: this._widgetDocs.getDescription(
TABLE_DOCS_PATH,
Expand Down Expand Up @@ -418,18 +463,41 @@ export class TableDemoService {
},
];

public defaults = getDefaultFromDemoConfig<TableInputControls>(this.inputControlConfig);
public readonly excludedDetailSymbol = signal('');

public model: TableInputControls = { ...this.defaults };
public readonly demoInputControlConfig = {
...this.inputControlConfig,
excludedDetailSymbol: {
description: 'Demo only: rows with this exact symbol have no detail row or trigger.',
type: 'string',
default: '',
control: DemoControl.TEXT,
onModelChange: (value?: string): void => this.excludedDetailSymbol.set(value ?? ''),
},
};

public defaults = getDefaultFromDemoConfig<TableInputControls & { excludedDetailSymbol: string }>(this.demoInputControlConfig);

public model = { ...this.defaults };

public reset(): void {
this.model = { ...this.defaults };
this.excludedDetailSymbol.set(this.defaults.excludedDetailSymbol);
}

public getMethodConfig(): DemoMethodConfig[] {
return [this.methodControlConfig];
}

public readonly detailTriggerPropControlConfig: DemoControlConfig<unknown> = {
idsTableDetailTrigger: {
description: this._widgetDocs.getDescription('table/directives/detail-trigger.docs.json', 'idsTableDetailTrigger',
'Binds the detail context to a native button, with IDS-owned state and accessibility.'),
type: 'IdsTableDetailTrigger',
default: '-',
},
};

public readonly cellTemplatePropControlConfig: DemoControlConfig<unknown> = {
templateName: {
description: this._widgetDocs.getDescription(
Expand Down Expand Up @@ -460,6 +528,10 @@ export class TableDemoService {
title: getDemoApiTitle(this.translate, 'COMPONENTS.TABLE', 'API.PROPERTY_GROUP.CELL_TEMPLATE'),
config: this.cellTemplatePropControlConfig,
},
{
title: getDemoApiTitle(this.translate, 'COMPONENTS.TABLE', 'API.PROPERTY_GROUP.DETAIL_TRIGGER'),
config: this.detailTriggerPropControlConfig,
},
];
}

Expand Down
5 changes: 5 additions & 0 deletions projects/demo/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@
"LABEL": {
"DETAIL_HEADER": "Actions"
},
"DETAIL_TRIGGERS": {
"OPEN": "Show details",
"CLOSE": "Hide details"
},
"BUTTON": {
"EXPAND_DETAILS": "Expand details",
"COLLAPSE_DETAILS": "Collapse details",
Expand Down Expand Up @@ -1709,6 +1713,7 @@
"PROPERTY_GROUP": {
"TITLE": "title properties",
"CELL_TEMPLATE": "cell template directive properties",
"DETAIL_TRIGGER": "detail trigger directive properties",
"ERROR_DEFINITION": "error definition directive properties",
"PSEUDO_CHECKBOX": "pseudo-checkbox properties",
"HEADER": "header properties",
Expand Down
5 changes: 5 additions & 0 deletions projects/demo/src/assets/i18n/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@
"LABEL": {
"DETAIL_HEADER": "Műveletek"
},
"DETAIL_TRIGGERS": {
"OPEN": "Részletek megjelenítése",
"CLOSE": "Részletek elrejtése"
},
"BUTTON": {
"EXPAND_DETAILS": "Részletek kinyitása",
"COLLAPSE_DETAILS": "Részletek becsukása",
Expand Down Expand Up @@ -1705,6 +1709,7 @@
"PROPERTY_GROUP": {
"TITLE": "címsor tulajdonságai",
"CELL_TEMPLATE": "cellasablon direktíva tulajdonságai",
"DETAIL_TRIGGER": "részletező trigger direktíva tulajdonságai",
"ERROR_DEFINITION": "hibadefiníció direktíva tulajdonságai",
"PSEUDO_CHECKBOX": "pszeudo-checkbox tulajdonságai",
"HEADER": "fejléc tulajdonságai",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
<ng-content select="[colStart]" />

@if (_cellRendererComp()) {
<ng-container *ngComponentOutlet="_cellRendererComp(); inputs: _cellRendererInputs()" />
} @else {
@switch (_cellTemplateName()) {
@case ('strong') {
<strong class="ids-cell-content ids-cell-content__strong">{{ _cellValue() }}</strong>
}
@case ('numeric') {
<div class="ids-cell-content ids-cell-content__numeric">{{ _numericCellValue() }}</div>
}
@case ('icon') {
<ids-icon
class="ids-cell-content ids-cell-content__icon"
[fontIcon]="_stringCellValue()"
[aria-hidden]="true"
/>
}
<!-- TODO: label-description template (needs further discussion) -->
@default {
@let template = _cellTemplate();
@if (template) {
<!-- external template ref -->
<ng-container *ngTemplateOutlet="template; context: { $implicit: rowData(), colDef: colDef(), cellValue: _cellValue() }" />
} @else {
<!-- default basic cell content -->
<div class="ids-cell-content ids-cell-content__default">{{ _cellValue() }}</div>
}
}
}
}

<ng-content select="[colEnd]" />
<ng-content select="[colStart]" />

@if (detailTemplate(); as template) {
<ng-container *ngTemplateOutlet="template; context: _templateContext()" />
} @else if (defaultDetailToggle()) {
<div class="ids-table__cell-content--with-detail-toggle">
@if (detailTogglePosition() === 'before') {
<ng-container *ngTemplateOutlet="defaultDetailToggle(); context: { $implicit: detailTrigger(), reserveSpace: true }" />
}
<div class="ids-table__detail-cell-content">
<ng-container *ngTemplateOutlet="cellContent" />
</div>
@if (detailTogglePosition() === 'after') {
<ng-container *ngTemplateOutlet="defaultDetailToggle(); context: { $implicit: detailTrigger(), reserveSpace: true }" />
}
</div>
} @else {
<ng-container *ngTemplateOutlet="cellContent" />
}

<ng-content select="[colEnd]" />

<ng-template #cellContent>
@if (_cellRendererComp()) {
<ng-container *ngComponentOutlet="_cellRendererComp(); inputs: _cellRendererInputs()" />
} @else {
@switch (_cellTemplateName()) {
@case ('strong') {
<strong class="ids-cell-content ids-cell-content__strong">{{ _cellValue() }}</strong>
}
@case ('numeric') {
<div class="ids-cell-content ids-cell-content__numeric">{{ _numericCellValue() }}</div>
}
@case ('icon') {
<ids-icon
class="ids-cell-content ids-cell-content__icon"
[fontIcon]="_stringCellValue()"
[aria-hidden]="true"
/>
}
@default {
@let template = _cellTemplate();
@if (template) {
<ng-container *ngTemplateOutlet="template; context: _templateContext()" />
} @else {
<div class="ids-cell-content ids-cell-content__default">{{ _cellValue() }}</div>
}
}
}
}
</ng-template>
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { IdsTableCellRenderer } from '../../directives/cell-renderer';
import { IdsTableIntl } from '../../table-intl';
import { IdsTableDetailTogglePosition, IdsTableDetailTogglePositionType } from '../../types/table-detail-toggle-position.type';
import { IdsTableDetailTrigger } from '../../types/table-detail-trigger';

import { NgComponentOutlet, NgTemplateOutlet } from '@angular/common';
import { Component, computed, ElementRef, inject, input, signal, TemplateRef } from '@angular/core';
Expand Down Expand Up @@ -28,6 +30,19 @@ export class IdsCellContentComponent<D> extends IdsTableCellRenderer<D> {
private _numberFormat = toSignal(this._intl.changes.pipe(map(() => this._intl.numberFormat), startWith(this._intl.numberFormat)));

public externalCellTemplates = input<Map<string, TemplateRef<unknown>>>();
public rowIndex = input<number>();
public detailTrigger = input<IdsTableDetailTrigger | null>(null);
public detailTemplate = input<TemplateRef<unknown> | null>(null);
public defaultDetailToggle = input<TemplateRef<unknown> | null>(null);
public detailTogglePosition = input<IdsTableDetailTogglePositionType>(IdsTableDetailTogglePosition.BEFORE);

protected _templateContext = computed(() => ({
$implicit: this.rowData(),
index: this.rowIndex(),
colDef: this.colDef(),
cellValue: this._cellValue(),
detail: this.detailTrigger(),
}));

private _cellRendererDef = computed(() => (this._isHeader ? this.colDef().headerCellRenderer : this.colDef().cellRenderer));
private _updateCellContent = signal(true);
Expand Down
9 changes: 9 additions & 0 deletions projects/widgets/table/directives/detail-trigger.docs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"idsTableDetailTrigger": {
"description": {
"en": "Bind the custom cell context's detail object to a native button (including button[idsChip] and button[idsIconButton]). IDS owns toggling and aria-expanded/aria-disabled. Native Enter/Space activation is preserved without extra key handlers. Supply visible button text or an aria-label for icon-only controls. Click and key events bubble to the cell/row as with the default trigger. Non-button hosts are not supported.",
"hu": "Az egyedi cellakontextus detail objektumát natív gombhoz kell kötni (button[idsChip] és button[idsIconButton] is használható). A nyitást és az aria-expanded/aria-disabled értékét az IDS kezeli. Az Enter/Space natív működése külön billentyűkezelő nélkül megmarad. A gomb kapjon látható szöveget, csak ikon esetén aria-labelt. A kattintás és billentyűesemények az alap triggerhez hasonlóan továbbterjednek a cellára/sorra. Nem button elem nem támogatott."
},
"type": "IdsTableDetailTrigger"
}
}
Loading
Loading