@muxima-ui/core 1.2.0 → 1.4.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.
@@ -0,0 +1,1187 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, inject, TemplateRef, Directive, model, booleanAttribute, numberAttribute, output, contentChildren, viewChild, ElementRef, Injector, signal, computed, afterRenderEffect, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgTemplateOutlet } from '@angular/common';
4
+ import { LiveAnnouncer } from '@angular/cdk/a11y';
5
+ import { MxCheckbox } from '@muxima-ui/core/checkbox';
6
+ import { mxUniqueId, mxNormalize } from '@muxima-ui/core/form-field';
7
+ import * as i1 from '@muxima-ui/core/menu';
8
+ import { MX_MENU } from '@muxima-ui/core/menu';
9
+
10
+ /**
11
+ * Modelo de uma célula: `<ng-template mxGridCell="estado" let-row let-value="value">`.
12
+ */
13
+ class MxGridCell {
14
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : /* istanbul ignore next */ {}), alias: 'mxGridCell' });
15
+ template = inject(TemplateRef);
16
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxGridCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
17
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.25", type: MxGridCell, isStandalone: true, selector: "ng-template[mxGridCell]", inputs: { key: { classPropertyName: "key", publicName: "mxGridCell", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
18
+ }
19
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxGridCell, decorators: [{
20
+ type: Directive,
21
+ args: [{ selector: 'ng-template[mxGridCell]' }]
22
+ }], propDecorators: { key: [{ type: i0.Input, args: [{ isSignal: true, alias: "mxGridCell", required: true }] }] } });
23
+ /** Conteúdo da barra por cima da grelha (pesquisa, botões). */
24
+ class MxGridToolbar {
25
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxGridToolbar, deps: [], target: i0.ɵɵFactoryTarget.Directive });
26
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.25", type: MxGridToolbar, isStandalone: true, selector: "[mxGridToolbar]", host: { classAttribute: "mx-grid-toolbar-slot" }, ngImport: i0 });
27
+ }
28
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxGridToolbar, decorators: [{
29
+ type: Directive,
30
+ args: [{ selector: '[mxGridToolbar]', host: { class: 'mx-grid-toolbar-slot' } }]
31
+ }] });
32
+ const HEADER_ROWS_BASE = 1;
33
+ /** Aplica o filtro de uma coluna numérica: 100, >100, >=100, <100, <=100, 10-20. */
34
+ function numberMatch(value, expr) {
35
+ const n = typeof value === 'number' ? value : Number(String(value).replace(/\s/g, '').replace(',', '.'));
36
+ if (Number.isNaN(n))
37
+ return false;
38
+ const e = expr.replace(/\s/g, '').replace(',', '.');
39
+ let m;
40
+ if ((m = e.match(/^(-?[\d.]+)-(-?[\d.]+)$/)))
41
+ return n >= Number(m[1]) && n <= Number(m[2]);
42
+ if ((m = e.match(/^(>=|<=|>|<|=)?(-?[\d.]+)$/))) {
43
+ const v = Number(m[2]);
44
+ switch (m[1]) {
45
+ case '>':
46
+ return n > v;
47
+ case '>=':
48
+ return n >= v;
49
+ case '<':
50
+ return n < v;
51
+ case '<=':
52
+ return n <= v;
53
+ default:
54
+ return n === v;
55
+ }
56
+ }
57
+ return true;
58
+ }
59
+ /**
60
+ * Grelha de dados (padrão WAI-ARIA Grid): ordenação, filtros por coluna, pesquisa,
61
+ * selecção, colunas redimensionáveis, fixas e escondíveis, e scroll virtual para
62
+ * milhares de linhas. Navega-se célula a célula com as setas.
63
+ *
64
+ * ```html
65
+ * <mx-data-grid [data]="facturas" [columns]="colunas" rowKey="numero"
66
+ * selection="multiple" [(selected)]="escolhidas" height="480px" aria-label="Facturas">
67
+ * <ng-template mxGridCell="estado" let-row>
68
+ * <span mxBadge [variant]="cor(row.estado)">{{ row.estado }}</span>
69
+ * </ng-template>
70
+ * </mx-data-grid>
71
+ * ```
72
+ *
73
+ * Com `manual`, a grelha não ordena nem filtra: emite [(sort)] e [(filters)] para o
74
+ * servidor e mostra `data` tal como chega (indica o total em `totalRows`).
75
+ */
76
+ class MxDataGrid {
77
+ data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
78
+ columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
79
+ /** Chave única de cada linha: nome do campo ou função. */
80
+ rowKey = input('id', ...(ngDevMode ? [{ debugName: "rowKey" }] : /* istanbul ignore next */ []));
81
+ selection = input('none', ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
82
+ /** Chaves das linhas seleccionadas. */
83
+ selected = model([], ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
84
+ sort = model({ key: null, direction: 'asc' }, ...(ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []));
85
+ /** Texto de cada filtro, por chave de coluna. */
86
+ filters = model({}, ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
87
+ /** Pesquisa em todas as colunas visíveis. */
88
+ query = input('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
89
+ /** Não ordena nem filtra: só emite sort e filters (dados do servidor). */
90
+ manual = input(false, { ...(ngDevMode ? { debugName: "manual" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
91
+ /** Total de linhas no servidor (modo manual), para aria-rowcount e o estado. */
92
+ totalRows = input(null, ...(ngDevMode ? [{ debugName: "totalRows" }] : /* istanbul ignore next */ []));
93
+ height = input(null, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
94
+ maxHeight = input(null, ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
95
+ rowHeight = input(40, { ...(ngDevMode ? { debugName: "rowHeight" } : /* istanbul ignore next */ {}), transform: numberAttribute });
96
+ /** Scroll virtual: `auto` liga a partir de 100 linhas (precisa de height). */
97
+ virtual = input('auto', ...(ngDevMode ? [{ debugName: "virtual" }] : /* istanbul ignore next */ []));
98
+ density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : /* istanbul ignore next */ []));
99
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
100
+ columnMenu = input(true, { ...(ngDevMode ? { debugName: "columnMenu" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
101
+ ariaLabel = input('Tabela de dados', { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
102
+ /** Coluna cujo texto identifica a linha nos nomes ("Seleccionar FT 2026/0201"). */
103
+ labelKey = input(null, ...(ngDevMode ? [{ debugName: "labelKey" }] : /* istanbul ignore next */ []));
104
+ emptyText = input('Sem resultados.', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
105
+ loadingText = input('A carregar...', ...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
106
+ columnsText = input('Colunas', ...(ngDevMode ? [{ debugName: "columnsText" }] : /* istanbul ignore next */ []));
107
+ filterText = input('Filtrar', ...(ngDevMode ? [{ debugName: "filterText" }] : /* istanbul ignore next */ []));
108
+ allText = input('Todos', ...(ngDevMode ? [{ debugName: "allText" }] : /* istanbul ignore next */ []));
109
+ selectAllText = input('Seleccionar todas as linhas', ...(ngDevMode ? [{ debugName: "selectAllText" }] : /* istanbul ignore next */ []));
110
+ selectRowText = input('Seleccionar', ...(ngDevMode ? [{ debugName: "selectRowText" }] : /* istanbul ignore next */ []));
111
+ selectText = input('Selecção', ...(ngDevMode ? [{ debugName: "selectText" }] : /* istanbul ignore next */ []));
112
+ resizeHint = input('Alt e setas esquerda e direita mudam a largura da coluna.', ...(ngDevMode ? [{ debugName: "resizeHint" }] : /* istanbul ignore next */ []));
113
+ /** Enter ou duplo clique numa linha. */
114
+ rowActivate = output();
115
+ cells = contentChildren(MxGridCell, ...(ngDevMode ? [{ debugName: "cells" }] : /* istanbul ignore next */ []));
116
+ toolbarSlot = contentChildren(MxGridToolbar, ...(ngDevMode ? [{ debugName: "toolbarSlot" }] : /* istanbul ignore next */ []));
117
+ scroller = viewChild.required('scroller');
118
+ host = inject(ElementRef).nativeElement;
119
+ injector = inject(Injector);
120
+ announcer = inject(LiveAnnouncer);
121
+ checkWidth = 44;
122
+ resizeHintId = mxUniqueId('mx-grid-resize');
123
+ radioName = mxUniqueId('mx-grid-row');
124
+ widths = signal({}, ...(ngDevMode ? [{ debugName: "widths" }] : /* istanbul ignore next */ []));
125
+ hiddenKeys = signal(null, ...(ngDevMode ? [{ debugName: "hiddenKeys" }] : /* istanbul ignore next */ []));
126
+ scrollTop = signal(0, ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
127
+ viewport = signal(400, ...(ngDevMode ? [{ debugName: "viewport" }] : /* istanbul ignore next */ []));
128
+ /** Célula activa: linha e coluna na grelha (0 = cabeçalho). */
129
+ active = signal([0, 0], ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
130
+ showToolbar = computed(() => this.columnMenu() || this.toolbarSlot().length > 0, ...(ngDevMode ? [{ debugName: "showToolbar" }] : /* istanbul ignore next */ []));
131
+ offset = computed(() => (this.selection() === 'none' ? 0 : 1), ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
132
+ hasFilters = computed(() => this.columns().some((c) => c.filter), ...(ngDevMode ? [{ debugName: "hasFilters" }] : /* istanbul ignore next */ []));
133
+ headerRows = computed(() => HEADER_ROWS_BASE + (this.hasFilters() ? 1 : 0), ...(ngDevMode ? [{ debugName: "headerRows" }] : /* istanbul ignore next */ []));
134
+ hideableColumns = computed(() => this.columns().filter((c) => c.hideable !== false), ...(ngDevMode ? [{ debugName: "hideableColumns" }] : /* istanbul ignore next */ []));
135
+ visibleColumns = computed(() => {
136
+ const hidden = this.hiddenKeys();
137
+ const cols = this.columns().filter((c) => !(hidden ? hidden.has(c.key) : c.hidden));
138
+ return [
139
+ ...cols.filter((c) => c.pinned === 'start'),
140
+ ...cols.filter((c) => !c.pinned),
141
+ ...cols.filter((c) => c.pinned === 'end'),
142
+ ];
143
+ }, ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
144
+ colCount = computed(() => this.visibleColumns().length + this.offset(), ...(ngDevMode ? [{ debugName: "colCount" }] : /* istanbul ignore next */ []));
145
+ tableWidth = computed(() => this.visibleColumns().reduce((a, c) => a + this.widthOf(c), 0) +
146
+ (this.offset() ? this.checkWidth : 0), ...(ngDevMode ? [{ debugName: "tableWidth" }] : /* istanbul ignore next */ []));
147
+ /** Linhas depois de filtros, pesquisa e ordenação. */
148
+ rows = computed(() => {
149
+ const data = this.data();
150
+ if (this.manual())
151
+ return data;
152
+ const filters = Object.entries(this.filters()).filter(([, v]) => v?.trim());
153
+ const cols = this.columns();
154
+ const q = mxNormalize(this.query().trim());
155
+ let out = data.filter((row) => filters.every(([key, v]) => {
156
+ const c = cols.find((x) => x.key === key);
157
+ if (!c)
158
+ return true;
159
+ const value = this.valueOf(c, row);
160
+ if (c.filter === 'number')
161
+ return numberMatch(value, v);
162
+ if (c.filter === 'select')
163
+ return String(value ?? '') === v;
164
+ return mxNormalize(this.text(c, row)).includes(mxNormalize(v.trim()));
165
+ }));
166
+ if (q) {
167
+ const vis = this.visibleColumns();
168
+ out = out.filter((row) => vis.some((c) => mxNormalize(this.text(c, row)).includes(q)));
169
+ }
170
+ const s = this.sort();
171
+ const sc = s.key ? cols.find((c) => c.key === s.key) : null;
172
+ if (sc) {
173
+ const collator = new Intl.Collator('pt', { numeric: true, sensitivity: 'base' });
174
+ const dir = s.direction === 'asc' ? 1 : -1;
175
+ out = [...out].sort((a, b) => {
176
+ const x = this.valueOf(sc, a);
177
+ const y = this.valueOf(sc, b);
178
+ if (x == null && y == null)
179
+ return 0;
180
+ if (x == null)
181
+ return 1;
182
+ if (y == null)
183
+ return -1;
184
+ if (x instanceof Date && y instanceof Date)
185
+ return (x.getTime() - y.getTime()) * dir;
186
+ if (typeof x === 'number' && typeof y === 'number')
187
+ return (x - y) * dir;
188
+ return collator.compare(String(x), String(y)) * dir;
189
+ });
190
+ }
191
+ return out;
192
+ }, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
193
+ isVirtual = computed(() => {
194
+ const v = this.virtual();
195
+ return (v === 'auto' ? this.rows().length > 100 : v) && !!(this.height() || this.maxHeight());
196
+ }, ...(ngDevMode ? [{ debugName: "isVirtual" }] : /* istanbul ignore next */ []));
197
+ range = computed(() => {
198
+ const n = this.rows().length;
199
+ if (!this.isVirtual())
200
+ return { start: 0, end: n };
201
+ const h = this.rowHeight();
202
+ const buffer = 8;
203
+ const start = Math.max(0, Math.floor(this.scrollTop() / h) - buffer);
204
+ const end = Math.min(n, start + Math.ceil(this.viewport() / h) + buffer * 2);
205
+ return { start, end };
206
+ }, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
207
+ rendered = computed(() => {
208
+ const { start, end } = this.range();
209
+ return this.rows().slice(start, end);
210
+ }, ...(ngDevMode ? [{ debugName: "rendered" }] : /* istanbul ignore next */ []));
211
+ rowCount = computed(() => (this.totalRows() ?? this.rows().length) + this.headerRows(), ...(ngDevMode ? [{ debugName: "rowCount" }] : /* istanbul ignore next */ []));
212
+ selectedSet = computed(() => new Set(this.selected()), ...(ngDevMode ? [{ debugName: "selectedSet" }] : /* istanbul ignore next */ []));
213
+ allSelected = computed(() => this.rows().length > 0 && this.rows().every((r) => this.selectedSet().has(this.keyOf(r))), ...(ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []));
214
+ someSelected = computed(() => !this.allSelected() && this.rows().some((r) => this.selectedSet().has(this.keyOf(r))), ...(ngDevMode ? [{ debugName: "someSelected" }] : /* istanbul ignore next */ []));
215
+ statusText = computed(() => {
216
+ const fmt = new Intl.NumberFormat('pt-AO');
217
+ const total = this.totalRows() ?? this.rows().length;
218
+ const parts = [`${fmt.format(total)} ${total === 1 ? 'linha' : 'linhas'}`];
219
+ if (!this.manual() && this.rows().length !== this.data().length)
220
+ parts[0] += ` de ${fmt.format(this.data().length)}`;
221
+ const sel = this.selected().length;
222
+ if (this.selection() !== 'none' && sel)
223
+ parts.push(`${fmt.format(sel)} ${sel === 1 ? 'seleccionada' : 'seleccionadas'}`);
224
+ return parts.join(', ');
225
+ }, ...(ngDevMode ? [{ debugName: "statusText" }] : /* istanbul ignore next */ []));
226
+ constructor() {
227
+ // tabindex itinerante: só a célula activa (ou o controlo dentro dela) entra no Tab
228
+ afterRenderEffect({
229
+ write: () => {
230
+ const [r, c] = this.active();
231
+ this.rendered();
232
+ this.visibleColumns();
233
+ const table = this.host.querySelector('.mx-grid-table');
234
+ if (!table)
235
+ return;
236
+ let activeEl = table.querySelector(`[data-cell="${r}:${c}"]`);
237
+ if (!activeEl)
238
+ activeEl = table.querySelector('[data-cell]');
239
+ table.querySelectorAll('[data-cell]').forEach((cell) => {
240
+ const isActive = cell === activeEl;
241
+ const widget = this.widgetIn(cell);
242
+ cell.tabIndex = isActive && !widget ? 0 : -1;
243
+ cell
244
+ .querySelectorAll('button, a[href], input, select, textarea, [tabindex]')
245
+ .forEach((w) => {
246
+ if (w === cell)
247
+ return;
248
+ w.tabIndex = isActive && w === widget ? 0 : -1;
249
+ });
250
+ });
251
+ },
252
+ });
253
+ afterNextRender(() => this.measure());
254
+ }
255
+ // ---- utilitários ----
256
+ keyOf(row) {
257
+ const k = this.rowKey();
258
+ return typeof k === 'function' ? k(row) : row[k];
259
+ }
260
+ valueOf(c, row) {
261
+ return c.value ? c.value(row) : row[c.key];
262
+ }
263
+ text(c, row) {
264
+ const v = this.valueOf(c, row);
265
+ if (c.format)
266
+ return c.format(v, row);
267
+ if (v instanceof Date)
268
+ return v.toLocaleDateString('pt-AO');
269
+ if (typeof v === 'number')
270
+ return v.toLocaleString('pt-AO');
271
+ return v == null ? '' : String(v);
272
+ }
273
+ rowLabel(row) {
274
+ const key = this.labelKey() ?? this.visibleColumns()[0]?.key;
275
+ const c = this.columns().find((x) => x.key === key);
276
+ return c ? this.text(c, row) : String(this.keyOf(row));
277
+ }
278
+ cellTemplate(key) {
279
+ return this.cells().find((t) => t.key() === key)?.template ?? null;
280
+ }
281
+ widthOf(c) {
282
+ return this.widths()[c.key] ?? c.width ?? 160;
283
+ }
284
+ pinOffset(c) {
285
+ let left = this.offset() ? this.checkWidth : 0;
286
+ for (const x of this.visibleColumns()) {
287
+ if (x === c)
288
+ break;
289
+ if (x.pinned === 'start')
290
+ left += this.widthOf(x);
291
+ }
292
+ return left;
293
+ }
294
+ pinOffsetEnd(c) {
295
+ let right = 0;
296
+ const cols = [...this.visibleColumns()].reverse();
297
+ for (const x of cols) {
298
+ if (x === c)
299
+ break;
300
+ if (x.pinned === 'end')
301
+ right += this.widthOf(x);
302
+ }
303
+ return right;
304
+ }
305
+ optionsOf(c) {
306
+ if (c.filterOptions)
307
+ return c.filterOptions;
308
+ return [...new Set(this.data().map((r) => String(this.valueOf(c, r) ?? '')))]
309
+ .filter(Boolean)
310
+ .sort((a, b) => a.localeCompare(b, 'pt'));
311
+ }
312
+ isHidden(c) {
313
+ const hidden = this.hiddenKeys();
314
+ return hidden ? hidden.has(c.key) : !!c.hidden;
315
+ }
316
+ ariaSort(c) {
317
+ if (c.sortable === false)
318
+ return null;
319
+ const s = this.sort();
320
+ return s.key === c.key ? (s.direction === 'asc' ? 'ascending' : 'descending') : 'none';
321
+ }
322
+ isSelected(row) {
323
+ return this.selectedSet().has(this.keyOf(row));
324
+ }
325
+ // ---- acções ----
326
+ toggleSort(c) {
327
+ if (c.sortable === false)
328
+ return;
329
+ const s = this.sort();
330
+ const next = s.key !== c.key
331
+ ? { key: c.key, direction: 'asc' }
332
+ : s.direction === 'asc'
333
+ ? { key: c.key, direction: 'desc' }
334
+ : { key: null, direction: 'asc' };
335
+ this.sort.set(next);
336
+ this.announcer.announce(next.key
337
+ ? `Ordenado por ${c.header}, ${next.direction === 'asc' ? 'ascendente' : 'descendente'}`
338
+ : 'Sem ordenação');
339
+ }
340
+ setFilter(key, value) {
341
+ this.filters.update((f) => ({ ...f, [key]: value }));
342
+ this.scrollTo(0);
343
+ }
344
+ toggleColumn(key) {
345
+ const current = new Set(this.hiddenKeys() ??
346
+ this.columns()
347
+ .filter((c) => c.hidden)
348
+ .map((c) => c.key));
349
+ if (current.has(key))
350
+ current.delete(key);
351
+ else if (this.visibleColumns().length > 1)
352
+ current.add(key);
353
+ this.hiddenKeys.set(current);
354
+ }
355
+ toggleRow(row) {
356
+ const key = this.keyOf(row);
357
+ if (this.selection() === 'single') {
358
+ this.selected.set(this.isSelected(row) ? [] : [key]);
359
+ return;
360
+ }
361
+ const set = new Set(this.selected());
362
+ if (set.has(key))
363
+ set.delete(key);
364
+ else
365
+ set.add(key);
366
+ this.selected.set([...set]);
367
+ }
368
+ toggleAll() {
369
+ const keys = this.rows().map((r) => this.keyOf(r));
370
+ if (this.allSelected()) {
371
+ const drop = new Set(keys);
372
+ this.selected.set(this.selected().filter((k) => !drop.has(k)));
373
+ }
374
+ else {
375
+ this.selected.set([...new Set([...this.selected(), ...keys])]);
376
+ }
377
+ }
378
+ setWidth(key, width) {
379
+ const c = this.columns().find((x) => x.key === key);
380
+ const min = c?.minWidth ?? 64;
381
+ this.widths.update((w) => ({ ...w, [key]: Math.max(min, Math.round(width)) }));
382
+ }
383
+ resetWidth(c) {
384
+ this.widths.update((w) => {
385
+ const { [c.key]: _, ...rest } = w;
386
+ return rest;
387
+ });
388
+ }
389
+ startResize(event, c) {
390
+ event.preventDefault();
391
+ event.stopPropagation();
392
+ const startX = event.clientX;
393
+ const start = this.widthOf(c);
394
+ const move = (e) => this.setWidth(c.key, start + (e.clientX - startX));
395
+ const up = () => {
396
+ window.removeEventListener('pointermove', move);
397
+ window.removeEventListener('pointerup', up);
398
+ };
399
+ window.addEventListener('pointermove', move);
400
+ window.addEventListener('pointerup', up);
401
+ }
402
+ // ---- scroll ----
403
+ measure() {
404
+ const el = this.scroller().nativeElement;
405
+ this.viewport.set(el.clientHeight || 400);
406
+ }
407
+ onScroll() {
408
+ const el = this.scroller().nativeElement;
409
+ this.scrollTop.set(el.scrollTop);
410
+ if (el.clientHeight)
411
+ this.viewport.set(el.clientHeight);
412
+ }
413
+ scrollTo(top) {
414
+ const el = this.scroller().nativeElement;
415
+ el.scrollTop = top;
416
+ this.scrollTop.set(top);
417
+ }
418
+ // ---- teclado (padrão Grid) ----
419
+ widgetIn(cell) {
420
+ return cell.querySelector('input, select, textarea, button:not(.mx-grid-resize), a[href]');
421
+ }
422
+ parseCell(el) {
423
+ const cell = el?.closest('[data-cell]');
424
+ if (!cell)
425
+ return null;
426
+ const [r, c] = cell.dataset['cell'].split(':').map(Number);
427
+ return [r, c];
428
+ }
429
+ onFocusIn(event) {
430
+ const pos = this.parseCell(event.target);
431
+ if (pos && (pos[0] !== this.active()[0] || pos[1] !== this.active()[1]))
432
+ this.active.set(pos);
433
+ }
434
+ lastRow() {
435
+ return Math.max(this.headerRows() - 1, this.headerRows() + this.rows().length - 1);
436
+ }
437
+ /** Move a célula activa e põe-lhe o foco (fazendo scroll se for preciso). */
438
+ focusCell(row, col) {
439
+ const r = Math.min(Math.max(row, 0), this.lastRow());
440
+ const c = Math.min(Math.max(col, 0), this.colCount() - 1);
441
+ this.active.set([r, c]);
442
+ const dataIndex = r - this.headerRows();
443
+ if (this.isVirtual() && dataIndex >= 0) {
444
+ const el = this.scroller().nativeElement;
445
+ const h = this.rowHeight();
446
+ const headerH = el.querySelector('thead')?.getBoundingClientRect().height ?? 0;
447
+ const top = dataIndex * h;
448
+ if (top < el.scrollTop)
449
+ this.scrollTo(top);
450
+ else if (top + h > el.scrollTop + el.clientHeight - headerH)
451
+ this.scrollTo(top + h - el.clientHeight + headerH);
452
+ }
453
+ afterNextRender(() => {
454
+ const cell = this.host.querySelector(`[data-cell="${r}:${c}"]`);
455
+ if (!cell)
456
+ return;
457
+ (this.widgetIn(cell) ?? cell).focus();
458
+ cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
459
+ }, { injector: this.injector });
460
+ }
461
+ onKeydown(event) {
462
+ const pos = this.parseCell(event.target);
463
+ if (!pos)
464
+ return;
465
+ const [r, c] = pos;
466
+ const target = event.target;
467
+ const inText = target.matches('input:not([type=checkbox]):not([type=radio]), textarea');
468
+ const inSelect = target.matches('select');
469
+ const page = Math.max(1, Math.floor(this.scroller().nativeElement.clientHeight / this.rowHeight()) - 1);
470
+ const col = this.visibleColumns()[c - this.offset()];
471
+ const dataIndex = r - this.headerRows();
472
+ const row = dataIndex >= 0 ? this.rows()[dataIndex] : undefined;
473
+ // redimensionar com Alt + setas no cabeçalho
474
+ if (event.altKey &&
475
+ r === 0 &&
476
+ col &&
477
+ col.resizable !== false &&
478
+ (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
479
+ event.preventDefault();
480
+ this.setWidth(col.key, this.widthOf(col) + (event.key === 'ArrowRight' ? 16 : -16));
481
+ this.announcer.announce(`${col.header}, ${this.widthOf(col)} píxeis`);
482
+ return;
483
+ }
484
+ if (inSelect)
485
+ return;
486
+ if (inText && !['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown'].includes(event.key))
487
+ return;
488
+ const ctrl = event.ctrlKey || event.metaKey;
489
+ let next = null;
490
+ switch (event.key) {
491
+ case 'ArrowRight':
492
+ next = [r, c + 1];
493
+ break;
494
+ case 'ArrowLeft':
495
+ next = [r, c - 1];
496
+ break;
497
+ case 'ArrowDown':
498
+ next = [r + 1, c];
499
+ break;
500
+ case 'ArrowUp':
501
+ next = [r - 1, c];
502
+ break;
503
+ case 'Home':
504
+ next = ctrl ? [0, 0] : [r, 0];
505
+ break;
506
+ case 'End':
507
+ next = ctrl ? [this.lastRow(), this.colCount() - 1] : [r, this.colCount() - 1];
508
+ break;
509
+ case 'PageDown':
510
+ next = [Math.max(r, this.headerRows()) + page, c];
511
+ break;
512
+ case 'PageUp':
513
+ next = [Math.max(this.headerRows(), r - page), c];
514
+ break;
515
+ case ' ':
516
+ if (row && this.selection() !== 'none' && !target.matches('button, a, input')) {
517
+ event.preventDefault();
518
+ this.toggleRow(row);
519
+ }
520
+ return;
521
+ case 'Enter':
522
+ if (r === 0 && col) {
523
+ event.preventDefault();
524
+ this.toggleSort(col);
525
+ }
526
+ else if (row && !target.matches('button, a, input, select')) {
527
+ event.preventDefault();
528
+ this.rowActivate.emit(row);
529
+ }
530
+ return;
531
+ case 'a':
532
+ if (ctrl && this.selection() === 'multiple') {
533
+ event.preventDefault();
534
+ this.toggleAll();
535
+ }
536
+ return;
537
+ default:
538
+ return;
539
+ }
540
+ event.preventDefault();
541
+ this.focusCell(next[0], next[1]);
542
+ }
543
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxDataGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
544
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MxDataGrid, isStandalone: true, selector: "mx-data-grid", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, manual: { classPropertyName: "manual", publicName: "manual", isSignal: true, isRequired: false, transformFunction: null }, totalRows: { classPropertyName: "totalRows", publicName: "totalRows", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, columnMenu: { classPropertyName: "columnMenu", publicName: "columnMenu", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, columnsText: { classPropertyName: "columnsText", publicName: "columnsText", isSignal: true, isRequired: false, transformFunction: null }, filterText: { classPropertyName: "filterText", publicName: "filterText", isSignal: true, isRequired: false, transformFunction: null }, allText: { classPropertyName: "allText", publicName: "allText", isSignal: true, isRequired: false, transformFunction: null }, selectAllText: { classPropertyName: "selectAllText", publicName: "selectAllText", isSignal: true, isRequired: false, transformFunction: null }, selectRowText: { classPropertyName: "selectRowText", publicName: "selectRowText", isSignal: true, isRequired: false, transformFunction: null }, selectText: { classPropertyName: "selectText", publicName: "selectText", isSignal: true, isRequired: false, transformFunction: null }, resizeHint: { classPropertyName: "resizeHint", publicName: "resizeHint", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", sort: "sortChange", filters: "filtersChange", rowActivate: "rowActivate" }, host: { properties: { "attr.data-density": "density() === \"comfortable\" ? null : density()", "attr.aria-label": "null" }, classAttribute: "mx-grid" }, queries: [{ propertyName: "cells", predicate: MxGridCell, isSignal: true }, { propertyName: "toolbarSlot", predicate: MxGridToolbar, isSignal: true }], viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }], exportAs: ["mxDataGrid"], ngImport: i0, template: `
545
+ @if (showToolbar()) {
546
+ <div class="mx-grid-toolbar">
547
+ <ng-content select="[mxGridToolbar]" />
548
+ @if (columnMenu()) {
549
+ <button type="button" class="mx-grid-columns-button" [mxMenuTriggerFor]="colunas">
550
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
551
+ <path
552
+ d="M2.5 3.5h11v9h-11zM6.5 3.5v9M10 3.5v9"
553
+ stroke="currentColor"
554
+ stroke-width="1.3"
555
+ stroke-linejoin="round"
556
+ />
557
+ </svg>
558
+ {{ columnsText() }}
559
+ </button>
560
+ <ng-template #colunas>
561
+ <div mxMenu>
562
+ @for (c of hideableColumns(); track c.key) {
563
+ <button
564
+ mxMenuItemCheckbox
565
+ type="button"
566
+ [checked]="!isHidden(c)"
567
+ (mxMenuItemTriggered)="toggleColumn(c.key)"
568
+ >
569
+ {{ c.header }}
570
+ </button>
571
+ }
572
+ </div>
573
+ </ng-template>
574
+ }
575
+ </div>
576
+ }
577
+
578
+ <div
579
+ class="mx-grid-scroller"
580
+ #scroller
581
+ [style.height]="height()"
582
+ [style.max-height]="maxHeight()"
583
+ (scroll)="onScroll()"
584
+ >
585
+ <table
586
+ class="mx-grid-table"
587
+ role="grid"
588
+ [attr.aria-label]="ariaLabel()"
589
+ [attr.aria-rowcount]="rowCount()"
590
+ [attr.aria-colcount]="colCount()"
591
+ [attr.aria-multiselectable]="selection() === 'multiple' ? 'true' : null"
592
+ [attr.aria-busy]="loading() ? 'true' : null"
593
+ [style.width.px]="tableWidth()"
594
+ (keydown)="onKeydown($event)"
595
+ (focusin)="onFocusIn($event)"
596
+ >
597
+ <colgroup>
598
+ @if (selection() !== 'none') {
599
+ <col [style.width.px]="checkWidth" />
600
+ }
601
+ @for (c of visibleColumns(); track c.key) {
602
+ <col [style.width.px]="widthOf(c)" />
603
+ }
604
+ </colgroup>
605
+ <thead>
606
+ <tr aria-rowindex="1" class="mx-grid-header-row">
607
+ @if (selection() !== 'none') {
608
+ <th
609
+ class="mx-grid-check mx-grid-pinned-start"
610
+ scope="col"
611
+ aria-colindex="1"
612
+ [style.left.px]="0"
613
+ [attr.data-cell]="'0:0'"
614
+ >
615
+ @if (selection() === 'multiple') {
616
+ <input
617
+ type="checkbox"
618
+ mxCheckbox
619
+ [checked]="allSelected()"
620
+ [indeterminate]="someSelected()"
621
+ [attr.aria-label]="selectAllText()"
622
+ (change)="toggleAll()"
623
+ tabindex="-1"
624
+ />
625
+ } @else {
626
+ <span class="mx-visually-hidden">{{ selectText() }}</span>
627
+ }
628
+ </th>
629
+ }
630
+ @for (c of visibleColumns(); track c.key; let i = $index) {
631
+ <th
632
+ scope="col"
633
+ [attr.aria-colindex]="i + 1 + offset()"
634
+ [attr.aria-sort]="ariaSort(c)"
635
+ [attr.aria-describedby]="c.resizable !== false ? resizeHintId : null"
636
+ [attr.data-align]="c.align ?? null"
637
+ [attr.data-cell]="'0:' + (i + offset())"
638
+ [class.mx-grid-pinned-start]="c.pinned === 'start'"
639
+ [class.mx-grid-pinned-end]="c.pinned === 'end'"
640
+ [style.left.px]="c.pinned === 'start' ? pinOffset(c) : null"
641
+ [style.right.px]="c.pinned === 'end' ? pinOffsetEnd(c) : null"
642
+ >
643
+ @if (c.sortable !== false) {
644
+ <button type="button" class="mx-grid-sort" tabindex="-1" (click)="toggleSort(c)">
645
+ {{ c.header }}
646
+ <svg class="mx-sort-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
647
+ @switch (ariaSort(c)) {
648
+ @case ('ascending') {
649
+ <path
650
+ d="M8 12V4M4.5 7.5L8 4l3.5 3.5"
651
+ stroke="currentColor"
652
+ stroke-width="1.5"
653
+ stroke-linecap="round"
654
+ stroke-linejoin="round"
655
+ />
656
+ }
657
+ @case ('descending') {
658
+ <path
659
+ d="M8 4v8M4.5 8.5L8 12l3.5-3.5"
660
+ stroke="currentColor"
661
+ stroke-width="1.5"
662
+ stroke-linecap="round"
663
+ stroke-linejoin="round"
664
+ />
665
+ }
666
+ @default {
667
+ <path
668
+ d="M5 6.5L8 3.5l3 3M5 9.5l3 3 3-3"
669
+ stroke="currentColor"
670
+ stroke-width="1.5"
671
+ stroke-linecap="round"
672
+ stroke-linejoin="round"
673
+ />
674
+ }
675
+ }
676
+ </svg>
677
+ </button>
678
+ } @else {
679
+ <span class="mx-grid-header-text">{{ c.header }}</span>
680
+ }
681
+ @if (c.resizable !== false) {
682
+ <span
683
+ class="mx-grid-resize"
684
+ aria-hidden="true"
685
+ (pointerdown)="startResize($event, c)"
686
+ (dblclick)="resetWidth(c)"
687
+ ></span>
688
+ }
689
+ </th>
690
+ }
691
+ </tr>
692
+ @if (hasFilters()) {
693
+ <tr aria-rowindex="2" class="mx-grid-filter-row">
694
+ @if (selection() !== 'none') {
695
+ <td
696
+ class="mx-grid-check mx-grid-pinned-start"
697
+ aria-colindex="1"
698
+ [style.left.px]="0"
699
+ [attr.data-cell]="'1:0'"
700
+ ></td>
701
+ }
702
+ @for (c of visibleColumns(); track c.key; let i = $index) {
703
+ <td
704
+ [attr.aria-colindex]="i + 1 + offset()"
705
+ [attr.data-cell]="'1:' + (i + offset())"
706
+ [class.mx-grid-pinned-start]="c.pinned === 'start'"
707
+ [class.mx-grid-pinned-end]="c.pinned === 'end'"
708
+ [style.left.px]="c.pinned === 'start' ? pinOffset(c) : null"
709
+ [style.right.px]="c.pinned === 'end' ? pinOffsetEnd(c) : null"
710
+ >
711
+ @switch (c.filter) {
712
+ @case ('select') {
713
+ <select
714
+ class="mx-grid-filter"
715
+ tabindex="-1"
716
+ [attr.aria-label]="filterText() + ' ' + c.header"
717
+ [value]="filters()[c.key] || ''"
718
+ (change)="setFilter(c.key, $any($event.target).value)"
719
+ >
720
+ <option value="">{{ allText() }}</option>
721
+ @for (o of optionsOf(c); track o) {
722
+ <option [value]="o" [selected]="filters()[c.key] === o">{{ o }}</option>
723
+ }
724
+ </select>
725
+ }
726
+ @case ('text') {
727
+ <input
728
+ class="mx-grid-filter"
729
+ tabindex="-1"
730
+ type="search"
731
+ [attr.aria-label]="filterText() + ' ' + c.header"
732
+ [value]="filters()[c.key] || ''"
733
+ (input)="setFilter(c.key, $any($event.target).value)"
734
+ />
735
+ }
736
+ @case ('number') {
737
+ <input
738
+ class="mx-grid-filter"
739
+ tabindex="-1"
740
+ type="text"
741
+ inputmode="decimal"
742
+ placeholder=">, <, 10-20"
743
+ [attr.aria-label]="
744
+ filterText() +
745
+ ' ' +
746
+ c.header +
747
+ ', aceita maior que, menor que e intervalos'
748
+ "
749
+ [value]="filters()[c.key] || ''"
750
+ (input)="setFilter(c.key, $any($event.target).value)"
751
+ />
752
+ }
753
+ }
754
+ </td>
755
+ }
756
+ </tr>
757
+ }
758
+ </thead>
759
+ <tbody>
760
+ @if (range().start > 0) {
761
+ <tr class="mx-grid-spacer" aria-hidden="true">
762
+ <td [attr.colspan]="colCount()" [style.height.px]="range().start * rowHeight()"></td>
763
+ </tr>
764
+ }
765
+ @for (row of rendered(); track keyOf(row); let r = $index) {
766
+ @let ri = range().start + r;
767
+ <tr
768
+ [attr.aria-rowindex]="ri + headerRows() + 1"
769
+ [attr.aria-selected]="selection() === 'none' ? null : isSelected(row)"
770
+ [style.height.px]="rowHeight()"
771
+ (dblclick)="rowActivate.emit(row)"
772
+ >
773
+ @if (selection() !== 'none') {
774
+ <td
775
+ class="mx-grid-check mx-grid-pinned-start"
776
+ aria-colindex="1"
777
+ [style.left.px]="0"
778
+ [attr.data-cell]="ri + headerRows() + ':0'"
779
+ >
780
+ @if (selection() === 'multiple') {
781
+ <input
782
+ type="checkbox"
783
+ mxCheckbox
784
+ tabindex="-1"
785
+ [checked]="isSelected(row)"
786
+ [attr.aria-label]="selectRowText() + ' ' + rowLabel(row)"
787
+ (change)="toggleRow(row)"
788
+ />
789
+ } @else {
790
+ <input
791
+ type="radio"
792
+ class="mx-radio"
793
+ tabindex="-1"
794
+ [name]="radioName"
795
+ [checked]="isSelected(row)"
796
+ [attr.aria-label]="selectRowText() + ' ' + rowLabel(row)"
797
+ (change)="toggleRow(row)"
798
+ />
799
+ }
800
+ </td>
801
+ }
802
+ @for (c of visibleColumns(); track c.key; let i = $index) {
803
+ <td
804
+ [attr.aria-colindex]="i + 1 + offset()"
805
+ [attr.data-cell]="ri + headerRows() + ':' + (i + offset())"
806
+ [attr.data-align]="c.align ?? null"
807
+ [class.mx-grid-pinned-start]="c.pinned === 'start'"
808
+ [class.mx-grid-pinned-end]="c.pinned === 'end'"
809
+ [style.left.px]="c.pinned === 'start' ? pinOffset(c) : null"
810
+ [style.right.px]="c.pinned === 'end' ? pinOffsetEnd(c) : null"
811
+ >
812
+ @if (cellTemplate(c.key); as tpl) {
813
+ <ng-container
814
+ *ngTemplateOutlet="
815
+ tpl;
816
+ context: { $implicit: row, value: valueOf(c, row), column: c }
817
+ "
818
+ />
819
+ } @else {
820
+ {{ text(c, row) }}
821
+ }
822
+ </td>
823
+ }
824
+ </tr>
825
+ } @empty {
826
+ <tr>
827
+ <td
828
+ class="mx-grid-empty"
829
+ [attr.colspan]="colCount()"
830
+ [attr.data-cell]="headerRows() + ':0'"
831
+ >
832
+ {{ loading() ? loadingText() : emptyText() }}
833
+ </td>
834
+ </tr>
835
+ }
836
+ @if (range().end < rows().length) {
837
+ <tr class="mx-grid-spacer" aria-hidden="true">
838
+ <td
839
+ [attr.colspan]="colCount()"
840
+ [style.height.px]="(rows().length - range().end) * rowHeight()"
841
+ ></td>
842
+ </tr>
843
+ }
844
+ </tbody>
845
+ </table>
846
+ </div>
847
+
848
+ <div class="mx-grid-footer">
849
+ <span class="mx-grid-status" aria-live="polite">{{ statusText() }}</span>
850
+ <ng-content select="[mxGridFooter]" />
851
+ </div>
852
+ <span class="mx-visually-hidden" [id]="resizeHintId">{{ resizeHint() }}</span>
853
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: MxCheckbox, selector: "input[type=checkbox][mxCheckbox]", inputs: ["size", "indeterminate"], exportAs: ["mxCheckbox"] }, { kind: "directive", type: i1.MxMenuTrigger, selector: "[mxMenuTriggerFor]", exportAs: ["mxMenuTrigger"] }, { kind: "directive", type: i1.MxMenu, selector: "[mxMenu]", exportAs: ["mxMenu"] }, { kind: "directive", type: i1.MxMenuItemCheckbox, selector: "[mxMenuItemCheckbox]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
854
+ }
855
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxDataGrid, decorators: [{
856
+ type: Component,
857
+ args: [{
858
+ selector: 'mx-data-grid',
859
+ exportAs: 'mxDataGrid',
860
+ imports: [NgTemplateOutlet, MxCheckbox, MX_MENU],
861
+ changeDetection: ChangeDetectionStrategy.OnPush,
862
+ encapsulation: ViewEncapsulation.None,
863
+ host: {
864
+ class: 'mx-grid',
865
+ '[attr.data-density]': 'density() === "comfortable" ? null : density()',
866
+ '[attr.aria-label]': 'null',
867
+ },
868
+ template: `
869
+ @if (showToolbar()) {
870
+ <div class="mx-grid-toolbar">
871
+ <ng-content select="[mxGridToolbar]" />
872
+ @if (columnMenu()) {
873
+ <button type="button" class="mx-grid-columns-button" [mxMenuTriggerFor]="colunas">
874
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
875
+ <path
876
+ d="M2.5 3.5h11v9h-11zM6.5 3.5v9M10 3.5v9"
877
+ stroke="currentColor"
878
+ stroke-width="1.3"
879
+ stroke-linejoin="round"
880
+ />
881
+ </svg>
882
+ {{ columnsText() }}
883
+ </button>
884
+ <ng-template #colunas>
885
+ <div mxMenu>
886
+ @for (c of hideableColumns(); track c.key) {
887
+ <button
888
+ mxMenuItemCheckbox
889
+ type="button"
890
+ [checked]="!isHidden(c)"
891
+ (mxMenuItemTriggered)="toggleColumn(c.key)"
892
+ >
893
+ {{ c.header }}
894
+ </button>
895
+ }
896
+ </div>
897
+ </ng-template>
898
+ }
899
+ </div>
900
+ }
901
+
902
+ <div
903
+ class="mx-grid-scroller"
904
+ #scroller
905
+ [style.height]="height()"
906
+ [style.max-height]="maxHeight()"
907
+ (scroll)="onScroll()"
908
+ >
909
+ <table
910
+ class="mx-grid-table"
911
+ role="grid"
912
+ [attr.aria-label]="ariaLabel()"
913
+ [attr.aria-rowcount]="rowCount()"
914
+ [attr.aria-colcount]="colCount()"
915
+ [attr.aria-multiselectable]="selection() === 'multiple' ? 'true' : null"
916
+ [attr.aria-busy]="loading() ? 'true' : null"
917
+ [style.width.px]="tableWidth()"
918
+ (keydown)="onKeydown($event)"
919
+ (focusin)="onFocusIn($event)"
920
+ >
921
+ <colgroup>
922
+ @if (selection() !== 'none') {
923
+ <col [style.width.px]="checkWidth" />
924
+ }
925
+ @for (c of visibleColumns(); track c.key) {
926
+ <col [style.width.px]="widthOf(c)" />
927
+ }
928
+ </colgroup>
929
+ <thead>
930
+ <tr aria-rowindex="1" class="mx-grid-header-row">
931
+ @if (selection() !== 'none') {
932
+ <th
933
+ class="mx-grid-check mx-grid-pinned-start"
934
+ scope="col"
935
+ aria-colindex="1"
936
+ [style.left.px]="0"
937
+ [attr.data-cell]="'0:0'"
938
+ >
939
+ @if (selection() === 'multiple') {
940
+ <input
941
+ type="checkbox"
942
+ mxCheckbox
943
+ [checked]="allSelected()"
944
+ [indeterminate]="someSelected()"
945
+ [attr.aria-label]="selectAllText()"
946
+ (change)="toggleAll()"
947
+ tabindex="-1"
948
+ />
949
+ } @else {
950
+ <span class="mx-visually-hidden">{{ selectText() }}</span>
951
+ }
952
+ </th>
953
+ }
954
+ @for (c of visibleColumns(); track c.key; let i = $index) {
955
+ <th
956
+ scope="col"
957
+ [attr.aria-colindex]="i + 1 + offset()"
958
+ [attr.aria-sort]="ariaSort(c)"
959
+ [attr.aria-describedby]="c.resizable !== false ? resizeHintId : null"
960
+ [attr.data-align]="c.align ?? null"
961
+ [attr.data-cell]="'0:' + (i + offset())"
962
+ [class.mx-grid-pinned-start]="c.pinned === 'start'"
963
+ [class.mx-grid-pinned-end]="c.pinned === 'end'"
964
+ [style.left.px]="c.pinned === 'start' ? pinOffset(c) : null"
965
+ [style.right.px]="c.pinned === 'end' ? pinOffsetEnd(c) : null"
966
+ >
967
+ @if (c.sortable !== false) {
968
+ <button type="button" class="mx-grid-sort" tabindex="-1" (click)="toggleSort(c)">
969
+ {{ c.header }}
970
+ <svg class="mx-sort-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
971
+ @switch (ariaSort(c)) {
972
+ @case ('ascending') {
973
+ <path
974
+ d="M8 12V4M4.5 7.5L8 4l3.5 3.5"
975
+ stroke="currentColor"
976
+ stroke-width="1.5"
977
+ stroke-linecap="round"
978
+ stroke-linejoin="round"
979
+ />
980
+ }
981
+ @case ('descending') {
982
+ <path
983
+ d="M8 4v8M4.5 8.5L8 12l3.5-3.5"
984
+ stroke="currentColor"
985
+ stroke-width="1.5"
986
+ stroke-linecap="round"
987
+ stroke-linejoin="round"
988
+ />
989
+ }
990
+ @default {
991
+ <path
992
+ d="M5 6.5L8 3.5l3 3M5 9.5l3 3 3-3"
993
+ stroke="currentColor"
994
+ stroke-width="1.5"
995
+ stroke-linecap="round"
996
+ stroke-linejoin="round"
997
+ />
998
+ }
999
+ }
1000
+ </svg>
1001
+ </button>
1002
+ } @else {
1003
+ <span class="mx-grid-header-text">{{ c.header }}</span>
1004
+ }
1005
+ @if (c.resizable !== false) {
1006
+ <span
1007
+ class="mx-grid-resize"
1008
+ aria-hidden="true"
1009
+ (pointerdown)="startResize($event, c)"
1010
+ (dblclick)="resetWidth(c)"
1011
+ ></span>
1012
+ }
1013
+ </th>
1014
+ }
1015
+ </tr>
1016
+ @if (hasFilters()) {
1017
+ <tr aria-rowindex="2" class="mx-grid-filter-row">
1018
+ @if (selection() !== 'none') {
1019
+ <td
1020
+ class="mx-grid-check mx-grid-pinned-start"
1021
+ aria-colindex="1"
1022
+ [style.left.px]="0"
1023
+ [attr.data-cell]="'1:0'"
1024
+ ></td>
1025
+ }
1026
+ @for (c of visibleColumns(); track c.key; let i = $index) {
1027
+ <td
1028
+ [attr.aria-colindex]="i + 1 + offset()"
1029
+ [attr.data-cell]="'1:' + (i + offset())"
1030
+ [class.mx-grid-pinned-start]="c.pinned === 'start'"
1031
+ [class.mx-grid-pinned-end]="c.pinned === 'end'"
1032
+ [style.left.px]="c.pinned === 'start' ? pinOffset(c) : null"
1033
+ [style.right.px]="c.pinned === 'end' ? pinOffsetEnd(c) : null"
1034
+ >
1035
+ @switch (c.filter) {
1036
+ @case ('select') {
1037
+ <select
1038
+ class="mx-grid-filter"
1039
+ tabindex="-1"
1040
+ [attr.aria-label]="filterText() + ' ' + c.header"
1041
+ [value]="filters()[c.key] || ''"
1042
+ (change)="setFilter(c.key, $any($event.target).value)"
1043
+ >
1044
+ <option value="">{{ allText() }}</option>
1045
+ @for (o of optionsOf(c); track o) {
1046
+ <option [value]="o" [selected]="filters()[c.key] === o">{{ o }}</option>
1047
+ }
1048
+ </select>
1049
+ }
1050
+ @case ('text') {
1051
+ <input
1052
+ class="mx-grid-filter"
1053
+ tabindex="-1"
1054
+ type="search"
1055
+ [attr.aria-label]="filterText() + ' ' + c.header"
1056
+ [value]="filters()[c.key] || ''"
1057
+ (input)="setFilter(c.key, $any($event.target).value)"
1058
+ />
1059
+ }
1060
+ @case ('number') {
1061
+ <input
1062
+ class="mx-grid-filter"
1063
+ tabindex="-1"
1064
+ type="text"
1065
+ inputmode="decimal"
1066
+ placeholder=">, <, 10-20"
1067
+ [attr.aria-label]="
1068
+ filterText() +
1069
+ ' ' +
1070
+ c.header +
1071
+ ', aceita maior que, menor que e intervalos'
1072
+ "
1073
+ [value]="filters()[c.key] || ''"
1074
+ (input)="setFilter(c.key, $any($event.target).value)"
1075
+ />
1076
+ }
1077
+ }
1078
+ </td>
1079
+ }
1080
+ </tr>
1081
+ }
1082
+ </thead>
1083
+ <tbody>
1084
+ @if (range().start > 0) {
1085
+ <tr class="mx-grid-spacer" aria-hidden="true">
1086
+ <td [attr.colspan]="colCount()" [style.height.px]="range().start * rowHeight()"></td>
1087
+ </tr>
1088
+ }
1089
+ @for (row of rendered(); track keyOf(row); let r = $index) {
1090
+ @let ri = range().start + r;
1091
+ <tr
1092
+ [attr.aria-rowindex]="ri + headerRows() + 1"
1093
+ [attr.aria-selected]="selection() === 'none' ? null : isSelected(row)"
1094
+ [style.height.px]="rowHeight()"
1095
+ (dblclick)="rowActivate.emit(row)"
1096
+ >
1097
+ @if (selection() !== 'none') {
1098
+ <td
1099
+ class="mx-grid-check mx-grid-pinned-start"
1100
+ aria-colindex="1"
1101
+ [style.left.px]="0"
1102
+ [attr.data-cell]="ri + headerRows() + ':0'"
1103
+ >
1104
+ @if (selection() === 'multiple') {
1105
+ <input
1106
+ type="checkbox"
1107
+ mxCheckbox
1108
+ tabindex="-1"
1109
+ [checked]="isSelected(row)"
1110
+ [attr.aria-label]="selectRowText() + ' ' + rowLabel(row)"
1111
+ (change)="toggleRow(row)"
1112
+ />
1113
+ } @else {
1114
+ <input
1115
+ type="radio"
1116
+ class="mx-radio"
1117
+ tabindex="-1"
1118
+ [name]="radioName"
1119
+ [checked]="isSelected(row)"
1120
+ [attr.aria-label]="selectRowText() + ' ' + rowLabel(row)"
1121
+ (change)="toggleRow(row)"
1122
+ />
1123
+ }
1124
+ </td>
1125
+ }
1126
+ @for (c of visibleColumns(); track c.key; let i = $index) {
1127
+ <td
1128
+ [attr.aria-colindex]="i + 1 + offset()"
1129
+ [attr.data-cell]="ri + headerRows() + ':' + (i + offset())"
1130
+ [attr.data-align]="c.align ?? null"
1131
+ [class.mx-grid-pinned-start]="c.pinned === 'start'"
1132
+ [class.mx-grid-pinned-end]="c.pinned === 'end'"
1133
+ [style.left.px]="c.pinned === 'start' ? pinOffset(c) : null"
1134
+ [style.right.px]="c.pinned === 'end' ? pinOffsetEnd(c) : null"
1135
+ >
1136
+ @if (cellTemplate(c.key); as tpl) {
1137
+ <ng-container
1138
+ *ngTemplateOutlet="
1139
+ tpl;
1140
+ context: { $implicit: row, value: valueOf(c, row), column: c }
1141
+ "
1142
+ />
1143
+ } @else {
1144
+ {{ text(c, row) }}
1145
+ }
1146
+ </td>
1147
+ }
1148
+ </tr>
1149
+ } @empty {
1150
+ <tr>
1151
+ <td
1152
+ class="mx-grid-empty"
1153
+ [attr.colspan]="colCount()"
1154
+ [attr.data-cell]="headerRows() + ':0'"
1155
+ >
1156
+ {{ loading() ? loadingText() : emptyText() }}
1157
+ </td>
1158
+ </tr>
1159
+ }
1160
+ @if (range().end < rows().length) {
1161
+ <tr class="mx-grid-spacer" aria-hidden="true">
1162
+ <td
1163
+ [attr.colspan]="colCount()"
1164
+ [style.height.px]="(rows().length - range().end) * rowHeight()"
1165
+ ></td>
1166
+ </tr>
1167
+ }
1168
+ </tbody>
1169
+ </table>
1170
+ </div>
1171
+
1172
+ <div class="mx-grid-footer">
1173
+ <span class="mx-grid-status" aria-live="polite">{{ statusText() }}</span>
1174
+ <ng-content select="[mxGridFooter]" />
1175
+ </div>
1176
+ <span class="mx-visually-hidden" [id]="resizeHintId">{{ resizeHint() }}</span>
1177
+ `,
1178
+ }]
1179
+ }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }, { type: i0.Output, args: ["sortChange"] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }, { type: i0.Output, args: ["filtersChange"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], manual: [{ type: i0.Input, args: [{ isSignal: true, alias: "manual", required: false }] }], totalRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRows", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], columnMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnMenu", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], columnsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnsText", required: false }] }], filterText: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterText", required: false }] }], allText: [{ type: i0.Input, args: [{ isSignal: true, alias: "allText", required: false }] }], selectAllText: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllText", required: false }] }], selectRowText: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectRowText", required: false }] }], selectText: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectText", required: false }] }], resizeHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeHint", required: false }] }], rowActivate: [{ type: i0.Output, args: ["rowActivate"] }], cells: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MxGridCell), { isSignal: true }] }], toolbarSlot: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MxGridToolbar), { isSignal: true }] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }] } });
1180
+ const MX_DATA_GRID = [MxDataGrid, MxGridCell, MxGridToolbar];
1181
+
1182
+ /**
1183
+ * Generated bundle index. Do not edit.
1184
+ */
1185
+
1186
+ export { MX_DATA_GRID, MxDataGrid, MxGridCell, MxGridToolbar };
1187
+ //# sourceMappingURL=muxima-ui-core-data-grid.mjs.map