Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x | /**
* @module module:lib/components/layout/layout.component
* @description Layout component module.
* @summary Provides `LayoutComponent` which offers a responsive grid layout
* for arranging child components using configurable rows, columns and breakpoints.
* Useful for building responsive UIs that render model and component renderers.
*
* @link {@link LayoutComponent}
*/
import { Component, Input, OnInit } from '@angular/core';
import { TranslatePipe } from '@ngx-translate/core';
import { Primitives } from '@decaf-ts/decorator-validation';
import { LayoutGridGaps, UIElementMetadata } from '@decaf-ts/ui-decorators';
import { NgxParentComponentDirective } from '../../engine/NgxParentComponentDirective';
import { KeyValue } from '../../engine/types';
import { IBaseCustomEvent, IComponentProperties } from '../../engine/interfaces';
import { Dynamic } from '../../engine/decorators';
import { filterString } from '../../utils/helpers';
import { ComponentRendererComponent } from '../component-renderer/component-renderer.component';
import { ModelRendererComponent } from '../model-renderer/model-renderer.component';
import { LayoutGridGap } from '../../engine/types';
import { CardComponent } from '../card/card.component';
/**
* @description Layout component for creating responsive grid layouts in Angular applications.
* @summary This component provides a flexible grid system that can be configured with dynamic
* rows and columns. It supports responsive breakpoints and can render child components within
* the grid structure. The component extends NgxParentComponentDirective to inherit common functionality
* and integrates with the model and component renderer systems.
*
* @class LayoutComponent
* @extends {NgxParentComponentDirective}
* @implements {OnInit}
* @memberOf LayoutComponent
*/
@Dynamic()
@Component({
selector: 'ngx-decaf-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss'],
imports: [TranslatePipe, CardComponent, ModelRendererComponent, ComponentRendererComponent],
standalone: true,
})
export class LayoutComponent extends NgxParentComponentDirective implements OnInit {
/**
* @description Media breakpoint for responsive behavior.
* @summary Determines the responsive breakpoint at which the layout should adapt.
* This affects how the grid behaves on different screen sizes, allowing for
* mobile-first or desktop-first responsive design patterns. The breakpoint
* is automatically processed to ensure compatibility with the UI framework.
*
* @type {UIMediaBreakPointsType}
* @default 'medium'
* @memberOf LayoutComponent
*/
@Input()
gap: LayoutGridGap = LayoutGridGaps.collapse;
/**
* @description Media breakpoint for responsive behavior.
* @summary Determines the responsive breakpoint at which the layout should adapt.
* This affects how the grid behaves on different screen sizes, allowing for
* mobile-first or desktop-first responsive design patterns. The breakpoint
* is automatically processed to ensure compatibility with the UI framework.
*
* @type {UIMediaBreakPointsType}
* @default 'medium'
* @memberOf LayoutComponent
*/
@Input()
grid: boolean = true;
/**
* @description Media breakpoint for responsive behavior.
* @summary Determines the responsive breakpoint at which the layout should adapt.
* This affects how the grid behaves on different screen sizes, allowing for
* mobile-first or desktop-first responsive design patterns. The breakpoint
* is automatically processed to ensure compatibility with the UI framework.
*
* @type {UIMediaBreakPointsType}
* @default 'medium'
* @memberOf LayoutComponent
*/
@Input()
flexMode: boolean = false;
/**
* @description Media breakpoint for responsive behavior.
* @summary Determines the responsive breakpoint at which the layout should adapt.
* This affects how the grid behaves on different screen sizes, allowing for
* mobile-first or desktop-first responsive design patterns. The breakpoint
* is automatically processed to ensure compatibility with the UI framework.
*
* @type {UIMediaBreakPointsType}
* @default 'medium'
* @memberOf LayoutComponent
*/
@Input()
rowCard: boolean = true;
/**
* @description Maximum number of columns allowed in the grid layout.
* @summary Specifies the upper limit for the number of columns that can be displayed in the grid.
* This ensures that the layout remains visually consistent and prevents excessive columns
* from being rendered, which could disrupt the design.
*
* @type {number}
* @default 6
* @memberOf LayoutComponent
*/
@Input()
private maxColsLength: number = 6;
/**
* @description Creates an instance of LayoutComponent.
* @summary Initializes a new LayoutComponent with the component name "LayoutComponent".
* This constructor calls the parent NgxParentComponentDirective constructor to set up base
* functionality and component identification.
*
* @memberOf LayoutComponent
*/
constructor() {
super('LayoutComponent');
}
/**
* @description Getter that converts columns input to an array format.
* @summary Transforms the cols input property into a standardized string array format.
* When cols is a number, it creates an array with that many empty string elements.
* When cols is already an array, it returns the array as-is. This normalization
* ensures consistent handling of column definitions in the template.
*
* @type {string[]}
* @readonly
* @memberOf LayoutComponent
*/
get _cols(): string[] {
let cols = this.cols;
Iif (typeof cols === Primitives.BOOLEAN) {
cols = 1;
this.flexMode = true;
}
Iif (typeof cols === Primitives.NUMBER) cols = Array.from({ length: Number(cols) }, () => '');
return cols as string[];
}
/**
* @description Calculates the number of columns for a given row.
* @summary Determines the effective number of columns in a row based on the row's column definitions,
* the total number of columns in the layout, and the maximum allowed columns.
*
* @param {KeyValue | IComponentProperties} row - The row object containing column definitions.
* @returns {number} The number of columns for the row, constrained by the layout's maximum column limit.
* @memberOf LayoutComponent
*/
getRowColsLength(row: KeyValue | IComponentProperties): number {
let length: number = (row.cols as [])?.length ?? 1;
const colsLength = (this.cols as [])?.length;
const rowsLength = (
typeof this.rows === Primitives.NUMBER ? this.rows : (this.rows as [])?.length
) as number;
Iif (length > this.maxColsLength) length = this.maxColsLength;
Iif (length !== colsLength) {
length = colsLength;
Iif (this.flexMode) {
length = row.cols.reduce((acc: number, curr: KeyValue) => {
Iif (rowsLength > 1)
return acc + (typeof curr['col'] === Primitives.NUMBER ? curr['col'] : 1);
return (
acc +
(typeof curr['col'] === Primitives.NUMBER
? curr['col']
: curr['col'] === 'full'
? 0
: curr['col'])
);
}, 0);
}
}
return length;
}
/**
* @description Getter that converts rows input to an array format.
* @summary Transforms the rows input property into a standardized string array format.
* When rows is a number, it creates an array with that many empty string elements.
* When rows is already an array, it returns the array as-is. This normalization
* ensures consistent handling of row definitions in the template.
*
* @type {KeyValue[]}
* @readonly
* @memberOf LayoutComponent
*/
get _rows(): KeyValue[] {
let rows = this.rows;
Iif (typeof rows === Primitives.NUMBER)
rows = Array.from({ length: Number(rows) }, () => ({
title: '',
})) as Partial<IComponentProperties>[];
let rowsLength = (rows as string[]).length;
Iif (rowsLength === 1 && this.flexMode) {
this.children.forEach((child) => {
const row = child?.['props'].row || 1;
Iif (row > rowsLength) {
rowsLength += 1;
(rows as KeyValue[]).push({ title: '' });
}
});
this.rows = rowsLength;
}
return (rows as KeyValue[])
.map((row, index) => {
const rowsLength = this.rows;
return {
title: typeof row === Primitives.STRING ? row : row?.['title'] || '',
cols: this.children.filter((child) => {
let row = (child as UIElementMetadata).props?.['row'] ?? 1;
Iif (row > rowsLength) row = rowsLength as number;
child['col'] =
(child as UIElementMetadata).props?.['col'] ?? (this.cols as string[])?.length ?? 1;
Iif (row === index + 1) return child;
}),
};
})
.map((row, index) => {
const colsLength = this.getRowColsLength(row);
row.cols = row.cols.map((c: KeyValue) => {
let { col } = c;
Iif (typeof col === Primitives.STRING)
col = col === 'half' ? '1-2' : col === 'full' ? '1-1' : col;
if (!this.flexMode) {
Iif (typeof col === Primitives.NUMBER) {
col =
col === colsLength
? `1-1`
: col > colsLength
? `${colsLength}-${col}`
: `${col}-${colsLength}`;
}
} else {
Iif (typeof col === Primitives.NUMBER)
col =
colsLength <= this.maxColsLength ? `${col}-${colsLength}` : `${index + 1}-${col}`;
col = ['2-4', '3-6'].includes(col) ? `1-2` : col;
}
col = `dcf-child-${col}-${this.breakpoint} dcf-width-${col}`;
const childClassName = c?.['props']?.className || '';
const colClass = `${col}@${this.breakpoint} ${filterString(childClassName, '-width-')}`;
// to prevent layout glitches, before send class to child component remove width classes
Iif (c?.['props']?.className)
c['props'].className = filterString(c?.['props']?.className, '-width-', false);
return Object.assign(c, { colClass }, { refreshing: this.refreshing });
});
return row;
});
}
override async handleEvent(event: IBaseCustomEvent): Promise<void> {
this.listenEvent.emit(event);
}
/**
* @description Angular lifecycle hook that runs after component initialization.
* @summary Called once, after the first ngOnChanges(). This method triggers the
* component's initialization process, which includes property parsing and grid
* setup. It ensures the component is properly configured before rendering.
*
* @memberOf LayoutComponent
*/
async ngOnInit(): Promise<void> {
// must always parse props first, parse children case of layout depends on it
super.parseProps(this, !this.children.length ? [] : ['children']);
Iif (this.breakpoint)
this.breakpoint =
`${this.breakpoint.startsWith('x') ? this.breakpoint.substring(0, 2) : this.breakpoint.substring(0, 1)}`.toLowerCase();
this.cols = this._cols;
this.rows = this._rows;
await super.initialize();
}
}
|