@mozaic-ds/angular 2.0.44 → 2.0.46

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.
@@ -1,18 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, ChangeDetectionStrategy, Component, contentChild, ViewEncapsulation, signal, forwardRef, output, ContentChild, viewChild, inject, model, InjectionToken, HostListener, Injector, Injectable, TemplateRef, afterNextRender, Directive, linkedSignal, EnvironmentInjector, createComponent, ChangeDetectorRef, NgZone, viewChildren, EventEmitter, effect, Output, ContentChildren, ApplicationRef, Renderer2, ElementRef, Input, booleanAttribute, contentChildren, DestroyRef } from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component, contentChild, ViewEncapsulation, signal, forwardRef, output, ContentChild, viewChild, inject, model, InjectionToken, HostListener, Injector, Injectable, TemplateRef, afterNextRender, Directive, linkedSignal, EnvironmentInjector, createComponent, ChangeDetectorRef, NgZone, viewChildren, EventEmitter, effect, Output, ContentChildren, ApplicationRef, Renderer2, ElementRef, Input, booleanAttribute, contentChildren, DestroyRef, untracked } from '@angular/core';
3
3
  import { RouterLink, RouterLinkActive, RouterLinkWithHref } from '@angular/router';
4
4
  import { NgTemplateOutlet, NgClass, NgComponentOutlet, JsonPipe, DOCUMENT } from '@angular/common';
5
5
  import * as i1 from '@angular/forms';
6
6
  import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
7
- import { WarningCircle32, Uploading32, CheckCircle32, CrossCircleFilled20, Refresh32, Refresh20, Eye20, Upload24, Cross24, ChevronLeft24, ChevronRight24, ChevronLeft20, ChevronRight20, CrossCircleFilled24, More24, Less24, InfoCircle32, CrossCircle32, Cross20, CrossCircle24, ImageAlt32, ChevronDown24, CheckCircleFilled32, WarningCircleFilled32, CrossCircleFilled32, InfoCircleFilled32, SidebarExpand24, ChevronDown20, InfoCircleFilled24, WarningCircleFilled24, CheckCircleFilled24, ArrowBottomRight24, ArrowTopRight24, StarHalf32, StarFilled32, Star32, StarHalf24, StarFilled24, Star24, StarHalf20, StarFilled20, Star20, Check20, Check24, ArrowBack24, ArrowNext24, HelpCircle24, Menu24, Notification24, Search24, PauseCircle24, PlayCircle24, ChevronUp20, Settings20, ErrorFilled24, Drag20, ListAdd20, ViewGridX420, Filter20, FullscreenEnter20, FullscreenExit20, Download20, Keyboard20, CheckCircle24 } from '@mozaic-ds/icons-angular';
7
+ import { WarningCircle32, Uploading32, CheckCircle32, CrossCircleFilled20, Refresh32, Refresh20, Eye20, Upload24, Cross24, ChevronLeft24, ChevronRight24, ChevronLeft20, ChevronRight20, CrossCircleFilled24, More24, Less24, InfoCircle32, CrossCircle32, Cross20, CrossCircle24, ImageAlt32, ChevronDown24, CheckCircleFilled32, WarningCircleFilled32, CrossCircleFilled32, InfoCircleFilled32, SidebarExpand24, ChevronDown20, InfoCircleFilled24, WarningCircleFilled24, CheckCircleFilled24, ArrowBottomRight24, ArrowTopRight24, StarHalf32, StarFilled32, Star32, StarHalf24, StarFilled24, Star24, StarHalf20, StarFilled20, Star20, Check20, Check24, ArrowBack24, ArrowNext24, HelpCircle24, Menu24, Notification24, Search24, PauseCircle24, PlayCircle24, Folder48, Search48, Drag20, ListAdd20, ChevronUp20, Filter20, Settings20, ErrorFilled24, ViewGridX420, FullscreenEnter20, FullscreenExit20, Download20, Keyboard20, CheckCircle24 } from '@mozaic-ds/icons-angular';
8
8
  import { Overlay, OverlayConfig, OverlayPositionBuilder, CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
9
9
  import { CdkPortalOutlet, ComponentPortal } from '@angular/cdk/portal';
10
10
  import { Subject, take, tap, of, firstValueFrom } from 'rxjs';
11
11
  import parsePhoneNumberFromString, { getCountries, getExampleNumber, isValidPhoneNumber, getCountryCallingCode } from 'libphonenumber-js';
12
12
  import examples from 'libphonenumber-js/mobile/examples';
13
+ import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
13
14
  import * as i1$1 from '@angular/cdk/scrolling';
14
15
  import { VIRTUAL_SCROLL_STRATEGY, ScrollingModule } from '@angular/cdk/scrolling';
15
- import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
16
16
  import { ComponentHarness, TestKey } from '@angular/cdk/testing';
17
17
 
18
18
  class MozBreadcrumbComponent {
@@ -4887,8 +4887,11 @@ class GridStateManager {
4887
4887
  groupColumns = signal([], ...(ngDevMode ? [{ debugName: "groupColumns" }] : /* istanbul ignore next */ []));
4888
4888
  expandedGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedGroups" }] : /* istanbul ignore next */ []));
4889
4889
  // --- Filter ---
4890
- activeFilters = signal([], ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
4891
- filterDefs = signal([], ...(ngDevMode ? [{ debugName: "filterDefs" }] : /* istanbul ignore next */ []));
4890
+ /**
4891
+ * Unified filter state: single source of truth for the multi-condition
4892
+ * builder. The tag-bar displays a derived view via `FilterEngine.toLabel()`.
4893
+ */
4894
+ filterModel = signal({ conditions: [] }, ...(ngDevMode ? [{ debugName: "filterModel" }] : /* istanbul ignore next */ []));
4892
4895
  // --- Pagination ---
4893
4896
  pageIndex = signal(0, ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
4894
4897
  pageSize = signal(20, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
@@ -5168,87 +5171,257 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
5168
5171
  type: Injectable
5169
5172
  }] });
5170
5173
 
5174
+ /**
5175
+ * Filter model — multi-condition filter builder with AND/OR combinators.
5176
+ *
5177
+ * Evaluation is left-associative (no operator precedence). Grouped / parenthesised
5178
+ * conditions (`(a AND b) OR c`) are out of scope for the MVP; see docs.
5179
+ */
5180
+ /**
5181
+ * Default operator sets per data type. Consumers can restrict the set via
5182
+ * `ColumnDef.filterOperators`.
5183
+ */
5184
+ const DEFAULT_OPERATORS = {
5185
+ text: [
5186
+ 'contains',
5187
+ 'notContains',
5188
+ 'equals',
5189
+ 'notEquals',
5190
+ 'startsWith',
5191
+ 'endsWith',
5192
+ 'blank',
5193
+ 'notBlank',
5194
+ ],
5195
+ number: ['equals', 'notEquals', 'gt', 'gte', 'lt', 'lte', 'between', 'blank', 'notBlank'],
5196
+ date: ['equals', 'notEquals', 'gt', 'gte', 'lt', 'lte', 'between', 'blank', 'notBlank'],
5197
+ set: ['in', 'notIn', 'blank', 'notBlank'],
5198
+ boolean: ['equals', 'blank', 'notBlank'],
5199
+ };
5200
+ const DEFAULT_OPERATOR_PER_TYPE = {
5201
+ text: 'contains',
5202
+ number: 'equals',
5203
+ date: 'equals',
5204
+ set: 'in',
5205
+ boolean: 'equals',
5206
+ };
5207
+ /** Human-readable operator labels (used by `toLabel`). */
5208
+ const OPERATOR_LABELS = {
5209
+ contains: 'contains',
5210
+ notContains: 'does not contain',
5211
+ equals: 'equals',
5212
+ notEquals: 'does not equal',
5213
+ startsWith: 'starts with',
5214
+ endsWith: 'ends with',
5215
+ gt: '>',
5216
+ gte: '≥',
5217
+ lt: '<',
5218
+ lte: '≤',
5219
+ between: 'between',
5220
+ in: 'in',
5221
+ notIn: 'not in',
5222
+ blank: 'is blank',
5223
+ notBlank: 'is not blank',
5224
+ };
5225
+ /** Operators that do not require a user-entered value. */
5226
+ const VALUELESS_OPERATORS = new Set([
5227
+ 'blank',
5228
+ 'notBlank',
5229
+ ]);
5230
+ /** Operators that need a secondary value (`between`). */
5231
+ const RANGE_OPERATORS = new Set(['between']);
5232
+ /** Small helper for generating condition ids without pulling in a uuid dep. */
5233
+ function generateConditionId() {
5234
+ return `cond-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
5235
+ }
5236
+ /** True when a condition has enough information to participate in evaluation. */
5237
+ function isConditionComplete(condition) {
5238
+ if (!condition.field)
5239
+ return false;
5240
+ if (VALUELESS_OPERATORS.has(condition.operator))
5241
+ return true;
5242
+ const { value, valueTo } = condition.value;
5243
+ if (value == null || value === '')
5244
+ return false;
5245
+ if (RANGE_OPERATORS.has(condition.operator)) {
5246
+ if (valueTo == null || valueTo === '')
5247
+ return false;
5248
+ }
5249
+ return true;
5250
+ }
5251
+
5171
5252
  class FilterEngine {
5172
5253
  state = inject(GridStateManager);
5173
- setFilter(field, value, operator = 'contains') {
5174
- if (value === '' || value == null) {
5175
- this.removeFilter(field);
5176
- return;
5177
- }
5178
- const defMap = this.state.columnDefMap();
5179
- const def = defMap.get(field);
5180
- const label = `${def?.headerName ?? field}: ${String(value)}`;
5181
- this.state.activeFilters.update((filters) => {
5182
- const existing = filters.findIndex((f) => f.field === field);
5183
- const newFilter = { field, label, value, removable: true };
5184
- if (existing >= 0) {
5185
- const next = [...filters];
5186
- next[existing] = newFilter;
5187
- return next;
5188
- }
5189
- return [...filters, newFilter];
5190
- });
5191
- this.state.filterDefs.update((defs) => {
5192
- const existing = defs.findIndex((d) => d.field === field);
5193
- const newDef = { field, type: 'text', operator, value };
5194
- if (existing >= 0) {
5195
- const next = [...defs];
5196
- next[existing] = newDef;
5254
+ /** Latest mutation, used by the grid shell to emit `filterChange` once. */
5255
+ lastChange = signal(null, ...(ngDevMode ? [{ debugName: "lastChange" }] : /* istanbul ignore next */ []));
5256
+ conditions = computed(() => this.state.filterModel().conditions, ...(ngDevMode ? [{ debugName: "conditions" }] : /* istanbul ignore next */ []));
5257
+ hasActiveFilters = computed(() => this.conditions().length > 0, ...(ngDevMode ? [{ debugName: "hasActiveFilters" }] : /* istanbul ignore next */ []));
5258
+ lastEvent = this.lastChange.asReadonly();
5259
+ // ------------------------------------------------------------------
5260
+ // Mutations
5261
+ // ------------------------------------------------------------------
5262
+ /** Replaces the whole model in one go. Used by the drawer's Apply button. */
5263
+ setModel(model, reason = 'replace') {
5264
+ this.state.filterModel.set({ conditions: [...model.conditions] });
5265
+ this.state.pageIndex.set(0);
5266
+ this.notify(reason, null);
5267
+ }
5268
+ addCondition(condition) {
5269
+ this.state.filterModel.update((m) => ({
5270
+ conditions: [...m.conditions, condition],
5271
+ }));
5272
+ this.state.pageIndex.set(0);
5273
+ this.notify('add', condition);
5274
+ }
5275
+ updateCondition(id, patch) {
5276
+ let updated = null;
5277
+ this.state.filterModel.update((m) => ({
5278
+ conditions: m.conditions.map((c) => {
5279
+ if (c.id !== id)
5280
+ return c;
5281
+ const next = { ...c, ...patch, value: { ...c.value, ...(patch.value ?? {}) } };
5282
+ updated = next;
5197
5283
  return next;
5198
- }
5199
- return [...defs, newDef];
5284
+ }),
5285
+ }));
5286
+ this.state.pageIndex.set(0);
5287
+ if (updated)
5288
+ this.notify('update', updated);
5289
+ }
5290
+ removeCondition(id) {
5291
+ const removed = this.state.filterModel().conditions.find((c) => c.id === id) ?? null;
5292
+ this.state.filterModel.update((m) => ({
5293
+ conditions: m.conditions.filter((c) => c.id !== id),
5294
+ }));
5295
+ this.state.pageIndex.set(0);
5296
+ this.notify('remove', removed);
5297
+ }
5298
+ reorderConditions(fromIndex, toIndex) {
5299
+ this.state.filterModel.update((m) => {
5300
+ if (fromIndex === toIndex)
5301
+ return m;
5302
+ const next = [...m.conditions];
5303
+ const [item] = next.splice(fromIndex, 1);
5304
+ if (!item)
5305
+ return m;
5306
+ next.splice(toIndex, 0, item);
5307
+ return { conditions: next };
5200
5308
  });
5201
5309
  this.state.pageIndex.set(0);
5310
+ this.notify('reorder', null);
5202
5311
  }
5203
- removeFilter(field) {
5204
- this.state.activeFilters.update((filters) => filters.filter((f) => f.field !== field));
5205
- this.state.filterDefs.update((defs) => defs.filter((d) => d.field !== field));
5312
+ clearAll() {
5313
+ if (this.state.filterModel().conditions.length === 0)
5314
+ return;
5315
+ this.state.filterModel.set({ conditions: [] });
5206
5316
  this.state.pageIndex.set(0);
5317
+ this.notify('clear', null);
5207
5318
  }
5208
- clearAllFilters() {
5209
- this.state.activeFilters.set([]);
5210
- this.state.filterDefs.set([]);
5319
+ /** Convenience: drop all conditions that target a given field. */
5320
+ removeByField(field) {
5321
+ const before = this.state.filterModel().conditions;
5322
+ const after = before.filter((c) => c.field !== field);
5323
+ if (after.length === before.length)
5324
+ return;
5325
+ this.state.filterModel.set({ conditions: after });
5211
5326
  this.state.pageIndex.set(0);
5327
+ this.notify('replace', null);
5212
5328
  }
5329
+ // ------------------------------------------------------------------
5330
+ // Evaluation
5331
+ // ------------------------------------------------------------------
5332
+ /**
5333
+ * Evaluates the current model against the provided data. In `server` mode,
5334
+ * the grid delegates filtering to the consumer so we return the input as-is.
5335
+ */
5213
5336
  filterData(data) {
5214
- const defs = this.state.filterDefs();
5215
- if (defs.length === 0)
5337
+ if (this.state.mode() === 'server')
5338
+ return data;
5339
+ const conditions = this.state.filterModel().conditions.filter(isConditionComplete);
5340
+ if (conditions.length === 0)
5216
5341
  return data;
5217
5342
  const defMap = this.state.columnDefMap();
5343
+ const prepared = conditions.map((c) => ({
5344
+ cond: c,
5345
+ col: defMap.get(c.field),
5346
+ type: resolveFilterType(defMap.get(c.field)),
5347
+ }));
5218
5348
  return data.filter((row) => {
5219
- return defs.every((filterDef) => {
5220
- const colDef = defMap.get(filterDef.field);
5221
- const rawValue = colDef?.valueGetter
5222
- ? colDef.valueGetter(row)
5223
- : row[filterDef.field];
5224
- return this.matchFilter(rawValue, filterDef);
5225
- });
5349
+ let pass = matchOne(row, prepared[0].cond, prepared[0].col, prepared[0].type);
5350
+ for (let i = 1; i < prepared.length; i++) {
5351
+ const step = prepared[i];
5352
+ const result = matchOne(row, step.cond, step.col, step.type);
5353
+ pass = step.cond.combinator === 'and' ? pass && result : pass || result;
5354
+ }
5355
+ return pass;
5226
5356
  });
5227
5357
  }
5228
- matchFilter(value, filterDef) {
5229
- const filterValue = filterDef.value;
5230
- if (filterValue == null || filterValue === '')
5231
- return true;
5232
- const strValue = String(value ?? '').toLowerCase();
5233
- const strFilter = String(filterValue).toLowerCase();
5234
- switch (filterDef.operator) {
5235
- case 'contains':
5236
- return strValue.includes(strFilter);
5237
- case 'equals':
5238
- return strValue === strFilter;
5239
- case 'startsWith':
5240
- return strValue.startsWith(strFilter);
5241
- case 'gt':
5242
- return Number(value) > Number(filterValue);
5243
- case 'lt':
5244
- return Number(value) < Number(filterValue);
5245
- case 'in': {
5246
- const arr = Array.isArray(filterValue) ? filterValue : [filterValue];
5247
- return arr.some((v) => String(v).toLowerCase() === strValue);
5248
- }
5249
- default:
5250
- return true;
5358
+ // ------------------------------------------------------------------
5359
+ // Helpers
5360
+ // ------------------------------------------------------------------
5361
+ /** Returns a human-readable label for a condition ("Status equals En stock"). */
5362
+ toLabel(condition) {
5363
+ const def = this.state.columnDefMap().get(condition.field);
5364
+ const col = def?.headerName ?? condition.field;
5365
+ const op = OPERATOR_LABELS[condition.operator] ?? condition.operator;
5366
+ if (VALUELESS_OPERATORS.has(condition.operator)) {
5367
+ return `${col} ${op}`;
5368
+ }
5369
+ const value = formatValue(condition.value.value, def);
5370
+ if (RANGE_OPERATORS.has(condition.operator)) {
5371
+ const to = formatValue(condition.value.valueTo, def);
5372
+ return `${col} ${op} ${value} ${to}`;
5251
5373
  }
5374
+ return `${col} ${op} ${value}`;
5375
+ }
5376
+ /** Returns the filter data type inferred for a column. */
5377
+ getFilterType(field) {
5378
+ return resolveFilterType(this.state.columnDefMap().get(field));
5379
+ }
5380
+ /**
5381
+ * Builds the column descriptors consumed by the filter builder UI. The
5382
+ * returned list includes only filterable columns.
5383
+ */
5384
+ describeFilterableColumns() {
5385
+ const defs = this.state.columnDefs();
5386
+ return defs.filter((d) => d.filterable && !d.filterTemplate).map((d) => this.describeColumn(d));
5387
+ }
5388
+ describeColumn(def) {
5389
+ const type = resolveFilterType(def);
5390
+ const operators = def.filterOperators && def.filterOperators.length > 0
5391
+ ? def.filterOperators
5392
+ : DEFAULT_OPERATORS[type];
5393
+ const defaultOp = def.defaultFilterOperator ?? DEFAULT_OPERATOR_PER_TYPE[type];
5394
+ return {
5395
+ field: def.field,
5396
+ headerName: def.headerName ?? def.field,
5397
+ filterType: type,
5398
+ operators,
5399
+ defaultOperator: operators.includes(defaultOp) ? defaultOp : operators[0],
5400
+ options: def.filterOptions ?? inferOptionsFromData(this.state.sourceData(), def, type),
5401
+ };
5402
+ }
5403
+ /** Factory for new conditions created by the UI. */
5404
+ makeCondition(field, isFirst, overrides = {}) {
5405
+ const def = this.state.columnDefMap().get(field);
5406
+ const descriptor = def ? this.describeColumn(def) : null;
5407
+ return {
5408
+ id: generateConditionId(),
5409
+ combinator: isFirst ? 'and' : 'and',
5410
+ field,
5411
+ operator: descriptor?.defaultOperator ?? 'contains',
5412
+ value: {},
5413
+ ...overrides,
5414
+ };
5415
+ }
5416
+ // ------------------------------------------------------------------
5417
+ // Internal
5418
+ // ------------------------------------------------------------------
5419
+ notify(reason, condition) {
5420
+ this.lastChange.set({
5421
+ model: { conditions: this.state.filterModel().conditions.slice() },
5422
+ condition,
5423
+ reason,
5424
+ });
5252
5425
  }
5253
5426
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FilterEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5254
5427
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FilterEngine });
@@ -5256,6 +5429,181 @@ class FilterEngine {
5256
5429
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FilterEngine, decorators: [{
5257
5430
  type: Injectable
5258
5431
  }] });
5432
+ // --------------------------------------------------------------------
5433
+ // Pure helpers
5434
+ // --------------------------------------------------------------------
5435
+ function resolveFilterType(def) {
5436
+ if (!def)
5437
+ return 'text';
5438
+ if (def.filterType)
5439
+ return def.filterType;
5440
+ switch (def.cellEditor) {
5441
+ case 'number':
5442
+ return 'number';
5443
+ case 'date':
5444
+ return 'date';
5445
+ case 'select':
5446
+ return 'set';
5447
+ case 'checkbox':
5448
+ case 'toggle':
5449
+ return 'boolean';
5450
+ default:
5451
+ return 'text';
5452
+ }
5453
+ }
5454
+ function matchOne(row, condition, col, type) {
5455
+ const raw = col?.valueGetter
5456
+ ? col.valueGetter(row)
5457
+ : row[condition.field];
5458
+ const predicate = PREDICATES[type]?.[condition.operator];
5459
+ if (!predicate)
5460
+ return true;
5461
+ return predicate(raw, condition.value);
5462
+ }
5463
+ function formatValue(value, def) {
5464
+ if (value == null || value === '')
5465
+ return '';
5466
+ if (Array.isArray(value)) {
5467
+ return value.map((v) => formatSingleOption(v, def)).join(', ');
5468
+ }
5469
+ return formatSingleOption(value, def);
5470
+ }
5471
+ function formatSingleOption(value, def) {
5472
+ if (def?.filterOptions) {
5473
+ const opt = def.filterOptions.find((o) => String(o.value) === String(value));
5474
+ if (opt)
5475
+ return String(opt.label);
5476
+ }
5477
+ return String(value);
5478
+ }
5479
+ function inferOptionsFromData(data, def, type) {
5480
+ if (type !== 'set')
5481
+ return undefined;
5482
+ const seen = new Set();
5483
+ const out = [];
5484
+ for (const row of data) {
5485
+ const v = def.valueGetter ? def.valueGetter(row) : row[def.field];
5486
+ if (v == null)
5487
+ continue;
5488
+ const key = String(v);
5489
+ if (seen.has(key))
5490
+ continue;
5491
+ seen.add(key);
5492
+ out.push({ value: v, label: key });
5493
+ if (out.length >= 200)
5494
+ break;
5495
+ }
5496
+ return out;
5497
+ }
5498
+ // --------------------------------------------------------------------
5499
+ // Predicate table — dispatched by (filterType, operator)
5500
+ // --------------------------------------------------------------------
5501
+ const textContains = (cell, { value }) => String(cell ?? '')
5502
+ .toLowerCase()
5503
+ .includes(String(value ?? '').toLowerCase());
5504
+ const textNotContains = (cell, v) => !textContains(cell, v);
5505
+ const textEquals = (cell, { value }) => String(cell ?? '').toLowerCase() === String(value ?? '').toLowerCase();
5506
+ const textNotEquals = (cell, v) => !textEquals(cell, v);
5507
+ const textStartsWith = (cell, { value }) => String(cell ?? '')
5508
+ .toLowerCase()
5509
+ .startsWith(String(value ?? '').toLowerCase());
5510
+ const textEndsWith = (cell, { value }) => String(cell ?? '')
5511
+ .toLowerCase()
5512
+ .endsWith(String(value ?? '').toLowerCase());
5513
+ const blank = (cell) => cell == null || cell === '';
5514
+ const notBlank = (cell) => !blank(cell, { value: undefined });
5515
+ const numEquals = (cell, { value }) => toNum(cell) === toNum(value);
5516
+ const numNotEquals = (cell, v) => !numEquals(cell, v);
5517
+ const numGt = (cell, { value }) => toNum(cell) > toNum(value);
5518
+ const numGte = (cell, { value }) => toNum(cell) >= toNum(value);
5519
+ const numLt = (cell, { value }) => toNum(cell) < toNum(value);
5520
+ const numLte = (cell, { value }) => toNum(cell) <= toNum(value);
5521
+ const numBetween = (cell, { value, valueTo }) => {
5522
+ const n = toNum(cell);
5523
+ return n >= toNum(value) && n <= toNum(valueTo);
5524
+ };
5525
+ const dateEquals = (cell, { value }) => toTime(cell) === toTime(value);
5526
+ const dateNotEquals = (cell, v) => !dateEquals(cell, v);
5527
+ const dateGt = (cell, { value }) => toTime(cell) > toTime(value);
5528
+ const dateGte = (cell, { value }) => toTime(cell) >= toTime(value);
5529
+ const dateLt = (cell, { value }) => toTime(cell) < toTime(value);
5530
+ const dateLte = (cell, { value }) => toTime(cell) <= toTime(value);
5531
+ const dateBetween = (cell, { value, valueTo }) => {
5532
+ const t = toTime(cell);
5533
+ return t >= toTime(value) && t <= toTime(valueTo);
5534
+ };
5535
+ const setIn = (cell, { value }) => {
5536
+ const arr = Array.isArray(value) ? value : [value];
5537
+ if (arr.length === 0)
5538
+ return true;
5539
+ return arr.some((v) => String(v) === String(cell));
5540
+ };
5541
+ const setNotIn = (cell, v) => !setIn(cell, v);
5542
+ const boolEquals = (cell, { value }) => {
5543
+ const expected = value === true || value === 'true' || value === 1;
5544
+ const actual = cell === true || cell === 'true' || cell === 1;
5545
+ return expected === actual;
5546
+ };
5547
+ function toNum(v) {
5548
+ const n = typeof v === 'number' ? v : Number(v);
5549
+ return Number.isFinite(n) ? n : NaN;
5550
+ }
5551
+ function toTime(v) {
5552
+ if (v instanceof Date)
5553
+ return v.getTime();
5554
+ if (typeof v === 'number')
5555
+ return v;
5556
+ if (typeof v === 'string') {
5557
+ const parsed = Date.parse(v);
5558
+ return Number.isFinite(parsed) ? parsed : NaN;
5559
+ }
5560
+ return NaN;
5561
+ }
5562
+ const PREDICATES = {
5563
+ text: {
5564
+ contains: textContains,
5565
+ notContains: textNotContains,
5566
+ equals: textEquals,
5567
+ notEquals: textNotEquals,
5568
+ startsWith: textStartsWith,
5569
+ endsWith: textEndsWith,
5570
+ blank,
5571
+ notBlank,
5572
+ },
5573
+ number: {
5574
+ equals: numEquals,
5575
+ notEquals: numNotEquals,
5576
+ gt: numGt,
5577
+ gte: numGte,
5578
+ lt: numLt,
5579
+ lte: numLte,
5580
+ between: numBetween,
5581
+ blank,
5582
+ notBlank,
5583
+ },
5584
+ date: {
5585
+ equals: dateEquals,
5586
+ notEquals: dateNotEquals,
5587
+ gt: dateGt,
5588
+ gte: dateGte,
5589
+ lt: dateLt,
5590
+ lte: dateLte,
5591
+ between: dateBetween,
5592
+ blank,
5593
+ notBlank,
5594
+ },
5595
+ set: {
5596
+ in: setIn,
5597
+ notIn: setNotIn,
5598
+ blank,
5599
+ notBlank,
5600
+ },
5601
+ boolean: {
5602
+ equals: boolEquals,
5603
+ blank,
5604
+ notBlank,
5605
+ },
5606
+ };
5259
5607
 
5260
5608
  class GroupEngine {
5261
5609
  state = inject(GridStateManager);
@@ -7737,6 +8085,7 @@ class StatePersistenceEngine {
7737
8085
  pinned: col.pinned,
7738
8086
  })),
7739
8087
  sorts: this.state.activeSorts(),
8088
+ filters: this.state.filterModel().conditions.map(({ id: _id, ...rest }) => rest),
7740
8089
  };
7741
8090
  try {
7742
8091
  localStorage.setItem(storageKey, JSON.stringify(persisted));
@@ -7780,6 +8129,11 @@ class StatePersistenceEngine {
7780
8129
  : { ...s, sort: null, sortIndex: null };
7781
8130
  }));
7782
8131
  }
8132
+ if (persisted.filters?.length) {
8133
+ this.state.filterModel.set({
8134
+ conditions: persisted.filters.map((f) => ({ ...f, id: generateConditionId() })),
8135
+ });
8136
+ }
7783
8137
  return true;
7784
8138
  }
7785
8139
  catch {
@@ -8147,13 +8501,343 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8147
8501
  }]
8148
8502
  }], propDecorators: { slot: [{ type: i0.Input, args: [{ isSignal: true, alias: "mozGridToolbarDef", required: false }] }] } });
8149
8503
 
8504
+ /**
8505
+ * Marks a `<ng-template>` projected into `<moz-grid>` as the renderer for
8506
+ * the empty state. Register one or two templates:
8507
+ *
8508
+ * ```html
8509
+ * <moz-grid [data]="rows">
8510
+ * <ng-template mozGridEmptyDef>
8511
+ * <p>Aucune donnée pour le moment</p>
8512
+ * </ng-template>
8513
+ *
8514
+ * <ng-template mozGridEmptyDef="no-results" let-ctx>
8515
+ * <p>Aucun résultat pour vos filtres ({{ ctx.activeFilterCount }})</p>
8516
+ * <button (click)="ctx.clearFilters()">Réinitialiser</button>
8517
+ * </ng-template>
8518
+ * </moz-grid>
8519
+ * ```
8520
+ *
8521
+ * The implicit context (`let-ctx`) exposes `{ activeFilterCount, clearFilters }`.
8522
+ */
8523
+ class MozGridEmptyDef {
8524
+ kind = input('no-data', { ...(ngDevMode ? { debugName: "kind" } : /* istanbul ignore next */ {}), alias: 'mozGridEmptyDef' });
8525
+ template = inject(TemplateRef);
8526
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridEmptyDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8527
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: MozGridEmptyDef, isStandalone: true, selector: "[mozGridEmptyDef]", inputs: { kind: { classPropertyName: "kind", publicName: "mozGridEmptyDef", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
8528
+ }
8529
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridEmptyDef, decorators: [{
8530
+ type: Directive,
8531
+ args: [{
8532
+ selector: '[mozGridEmptyDef]',
8533
+ }]
8534
+ }], propDecorators: { kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "mozGridEmptyDef", required: false }] }] } });
8535
+
8536
+ /**
8537
+ * Default empty-state shown by `<moz-grid>` when the body has no rows.
8538
+ * Two visual variants are dispatched on the `kind` input:
8539
+ * - `'no-data'` : the dataset is empty (folder icon, neutral copy).
8540
+ * - `'no-results'` : filters/search produced zero rows (search icon, CTA).
8541
+ *
8542
+ * Consumers can fully replace this component by projecting a
8543
+ * `<ng-template mozGridEmptyDef>` into the grid; see `MozGridEmptyDef`.
8544
+ */
8545
+ class MozGridEmptyStateComponent {
8546
+ kind = input.required(...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
8547
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
8548
+ description = input('', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
8549
+ /**
8550
+ * Optional CTA label. When falsy the button is not rendered. The grid
8551
+ * shell wires this to "Clear filters" for the `no-results` variant.
8552
+ */
8553
+ actionLabel = input('', ...(ngDevMode ? [{ debugName: "actionLabel" }] : /* istanbul ignore next */ []));
8554
+ action = output();
8555
+ resolvedTitle = computed(() => {
8556
+ if (this.title())
8557
+ return this.title();
8558
+ return this.kind() === 'no-results' ? 'No matching results' : 'No data to display';
8559
+ }, ...(ngDevMode ? [{ debugName: "resolvedTitle" }] : /* istanbul ignore next */ []));
8560
+ resolvedDescription = computed(() => {
8561
+ if (this.description())
8562
+ return this.description();
8563
+ return this.kind() === 'no-results'
8564
+ ? 'Try adjusting your filters or clearing them to see more rows.'
8565
+ : 'There is nothing to show here yet.';
8566
+ }, ...(ngDevMode ? [{ debugName: "resolvedDescription" }] : /* istanbul ignore next */ []));
8567
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridEmptyStateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8568
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridEmptyStateComponent, isStandalone: true, selector: "moz-grid-empty-state", inputs: { kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, ngImport: i0, template: "<div\n class=\"grid-empty-state\"\n [class.grid-empty-state--no-results]=\"kind() === 'no-results'\"\n role=\"status\"\n aria-live=\"polite\"\n>\n <div class=\"grid-empty-state__icon\" aria-hidden=\"true\">\n @if (kind() === 'no-results') {\n <Search48 />\n } @else {\n <Folder48 />\n }\n </div>\n\n <p class=\"grid-empty-state__title\">{{ resolvedTitle() }}</p>\n\n @if (resolvedDescription()) {\n <p class=\"grid-empty-state__description\">{{ resolvedDescription() }}</p>\n } @if (actionLabel()) {\n <button moz-button [outlined]=\"true\" size=\"s\" (click)=\"action.emit()\">\n {{ actionLabel() }}\n </button>\n }\n</div>\n", styles: [".grid-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:8px;padding:32px 24px;width:100%;height:100%;min-height:240px;color:var(--Text-Secondary, #555)}.grid-empty-state__icon{color:var(--Neutral-Grey-400, #97a1a8);margin-bottom:4px;display:inline-flex}.grid-empty-state__title{margin:0;font-size:16px;font-weight:600;color:var(--Text-Primary, #222)}.grid-empty-state__description{margin:0;max-width:360px;font-size:14px;line-height:1.4}.grid-empty-state--no-results .grid-empty-state__icon{color:var(--Status-Standalone-element-Primary, #0071ce)}\n"], dependencies: [{ kind: "component", type: Folder48, selector: "Folder48", inputs: ["hostClass"] }, { kind: "component", type: Search48, selector: "Search48", inputs: ["hostClass"] }, { kind: "component", type: MozButtonComponent, selector: "button[moz-button]", inputs: ["appearance", "size", "disabled", "ghost", "outlined", "iconPosition", "type", "isLoading"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8569
+ }
8570
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridEmptyStateComponent, decorators: [{
8571
+ type: Component,
8572
+ args: [{ selector: 'moz-grid-empty-state', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Folder48, Search48, MozButtonComponent], template: "<div\n class=\"grid-empty-state\"\n [class.grid-empty-state--no-results]=\"kind() === 'no-results'\"\n role=\"status\"\n aria-live=\"polite\"\n>\n <div class=\"grid-empty-state__icon\" aria-hidden=\"true\">\n @if (kind() === 'no-results') {\n <Search48 />\n } @else {\n <Folder48 />\n }\n </div>\n\n <p class=\"grid-empty-state__title\">{{ resolvedTitle() }}</p>\n\n @if (resolvedDescription()) {\n <p class=\"grid-empty-state__description\">{{ resolvedDescription() }}</p>\n } @if (actionLabel()) {\n <button moz-button [outlined]=\"true\" size=\"s\" (click)=\"action.emit()\">\n {{ actionLabel() }}\n </button>\n }\n</div>\n", styles: [".grid-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:8px;padding:32px 24px;width:100%;height:100%;min-height:240px;color:var(--Text-Secondary, #555)}.grid-empty-state__icon{color:var(--Neutral-Grey-400, #97a1a8);margin-bottom:4px;display:inline-flex}.grid-empty-state__title{margin:0;font-size:16px;font-weight:600;color:var(--Text-Primary, #222)}.grid-empty-state__description{margin:0;max-width:360px;font-size:14px;line-height:1.4}.grid-empty-state--no-results .grid-empty-state__icon{color:var(--Status-Standalone-element-Primary, #0071ce)}\n"] }]
8573
+ }], propDecorators: { kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
8574
+
8575
+ class MozGridFilterBuilderComponent {
8576
+ model = input.required(...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
8577
+ availableColumns = input.required(...(ngDevMode ? [{ debugName: "availableColumns" }] : /* istanbul ignore next */ []));
8578
+ applyMode = input('auto', ...(ngDevMode ? [{ debugName: "applyMode" }] : /* istanbul ignore next */ []));
8579
+ /** Hint: when true, renders a "Show rows" sub-title (used inside the overlay). */
8580
+ showSubtitle = input(true, ...(ngDevMode ? [{ debugName: "showSubtitle" }] : /* istanbul ignore next */ []));
8581
+ modelChange = output();
8582
+ // Draft (what the user sees). Synced from `model` input on change.
8583
+ draft = signal([], ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
8584
+ constructor() {
8585
+ effect(() => {
8586
+ const incoming = this.model().conditions;
8587
+ // Avoid clobbering local mutations: only sync when the incoming model
8588
+ // differs by id-set or values from the current draft.
8589
+ const current = untracked(() => this.draft());
8590
+ if (!conditionsEqual(incoming, current)) {
8591
+ this.draft.set(incoming.map((c) => ({ ...c, value: { ...c.value } })));
8592
+ }
8593
+ });
8594
+ }
8595
+ columnsById = computed(() => {
8596
+ const m = new Map();
8597
+ for (const c of this.availableColumns())
8598
+ m.set(c.field, c);
8599
+ return m;
8600
+ }, ...(ngDevMode ? [{ debugName: "columnsById" }] : /* istanbul ignore next */ []));
8601
+ operatorLabels = OPERATOR_LABELS;
8602
+ // ------------------------------------------------------------------
8603
+ // Mutations
8604
+ // ------------------------------------------------------------------
8605
+ addCondition() {
8606
+ const cols = this.availableColumns();
8607
+ if (cols.length === 0)
8608
+ return;
8609
+ const first = cols[0];
8610
+ const isFirst = this.draft().length === 0;
8611
+ const condition = {
8612
+ id: generateConditionId(),
8613
+ combinator: isFirst ? 'and' : 'and',
8614
+ field: first.field,
8615
+ operator: first.defaultOperator,
8616
+ value: {},
8617
+ };
8618
+ this.draft.update((list) => [...list, condition]);
8619
+ this.commit();
8620
+ }
8621
+ removeCondition(id) {
8622
+ this.draft.update((list) => list.filter((c) => c.id !== id));
8623
+ this.commit();
8624
+ }
8625
+ onCombinatorChange(id, combinator) {
8626
+ this.draft.update((list) => list.map((c) => (c.id === id ? { ...c, combinator } : c)));
8627
+ this.commit();
8628
+ }
8629
+ onFieldChange(id, field) {
8630
+ const col = this.columnsById().get(field);
8631
+ this.draft.update((list) => list.map((c) => c.id === id
8632
+ ? {
8633
+ ...c,
8634
+ field,
8635
+ operator: col?.defaultOperator ?? c.operator,
8636
+ value: {},
8637
+ }
8638
+ : c));
8639
+ this.commit();
8640
+ }
8641
+ onOperatorChange(id, operator) {
8642
+ this.draft.update((list) => list.map((c) => (c.id === id ? { ...c, operator, value: resetValueFor(operator, c.value) } : c)));
8643
+ this.commit();
8644
+ }
8645
+ onValueChange(id, patch) {
8646
+ this.draft.update((list) => list.map((c) => c.id === id
8647
+ ? {
8648
+ ...c,
8649
+ value: { ...c.value, ...patch },
8650
+ }
8651
+ : c));
8652
+ this.commit();
8653
+ }
8654
+ onSetValueChange(id, event) {
8655
+ const select = event.target;
8656
+ const values = Array.from(select.selectedOptions).map((o) => o.value);
8657
+ this.onValueChange(id, { value: values });
8658
+ }
8659
+ onDrop(event) {
8660
+ this.draft.update((list) => {
8661
+ const next = [...list];
8662
+ moveItemInArray(next, event.previousIndex, event.currentIndex);
8663
+ return next;
8664
+ });
8665
+ this.commit();
8666
+ }
8667
+ // ------------------------------------------------------------------
8668
+ // Value editor helpers (for the template)
8669
+ // ------------------------------------------------------------------
8670
+ needsValue(op) {
8671
+ return !VALUELESS_OPERATORS.has(op);
8672
+ }
8673
+ needsRange(op) {
8674
+ return RANGE_OPERATORS.has(op);
8675
+ }
8676
+ getSelectedSetValues(condition) {
8677
+ const v = condition.value.value;
8678
+ if (Array.isArray(v))
8679
+ return v.map((x) => String(x));
8680
+ if (v == null || v === '')
8681
+ return [];
8682
+ return [String(v)];
8683
+ }
8684
+ isSetValueSelected(condition, value) {
8685
+ return this.getSelectedSetValues(condition).includes(String(value));
8686
+ }
8687
+ inputTypeFor(op, type) {
8688
+ if (type === 'number')
8689
+ return 'number';
8690
+ if (type === 'date')
8691
+ return 'date';
8692
+ return 'text';
8693
+ }
8694
+ // ------------------------------------------------------------------
8695
+ // Internals
8696
+ // ------------------------------------------------------------------
8697
+ commit() {
8698
+ this.modelChange.emit({ conditions: this.draft().map((c) => ({ ...c, value: { ...c.value } })) });
8699
+ }
8700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8701
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridFilterBuilderComponent, isStandalone: true, selector: "moz-grid-filter-builder", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, availableColumns: { classPropertyName: "availableColumns", publicName: "availableColumns", isSignal: true, isRequired: true, transformFunction: null }, applyMode: { classPropertyName: "applyMode", publicName: "applyMode", isSignal: true, isRequired: false, transformFunction: null }, showSubtitle: { classPropertyName: "showSubtitle", publicName: "showSubtitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { modelChange: "modelChange" }, ngImport: i0, template: "<div class=\"filter-builder\">\n @if (showSubtitle()) {\n <p class=\"filter-builder__subtitle\">Show rows</p>\n }\n\n <div class=\"filter-builder__rows\" cdkDropList (cdkDropListDropped)=\"onDrop($event)\">\n @for (cond of draft(); track cond.id; let idx = $index; let first = $first) {\n <div class=\"filter-builder__row\" cdkDrag>\n <!-- Combinator -->\n <div class=\"filter-builder__combinator\">\n @if (first) {\n <span class=\"filter-builder__combinator-label\">Where</span>\n } @else {\n <select\n class=\"filter-builder__select filter-builder__select--combinator\"\n [value]=\"cond.combinator\"\n [attr.aria-label]=\"'Combinator for condition ' + (idx + 1)\"\n (change)=\"onCombinatorChange(cond.id, $any($event.target).value)\"\n >\n <option value=\"and\">And</option>\n <option value=\"or\">Or</option>\n </select>\n }\n </div>\n\n <!-- Column -->\n <select\n class=\"filter-builder__select filter-builder__select--column\"\n [value]=\"cond.field\"\n [attr.aria-label]=\"'Column for condition ' + (idx + 1)\"\n (change)=\"onFieldChange(cond.id, $any($event.target).value)\"\n >\n @for (col of availableColumns(); track col.field) {\n <option [value]=\"col.field\">{{ col.headerName }}</option>\n }\n </select>\n\n <!-- Operator -->\n <select\n class=\"filter-builder__select filter-builder__select--operator\"\n [value]=\"cond.operator\"\n [attr.aria-label]=\"'Operator for condition ' + (idx + 1)\"\n (change)=\"onOperatorChange(cond.id, $any($event.target).value)\"\n >\n @for (op of columnsById().get(cond.field)?.operators ?? []; track op) {\n <option [value]=\"op\">{{ operatorLabels[op] }}</option>\n }\n </select>\n\n <!-- Value -->\n <div class=\"filter-builder__value\">\n @if (needsValue(cond.operator)) { @let col = columnsById().get(cond.field); @if\n (col?.filterType === 'set') {\n <select\n class=\"filter-builder__select filter-builder__select--set\"\n multiple\n size=\"4\"\n [attr.aria-label]=\"'Values for condition ' + (idx + 1)\"\n (change)=\"onSetValueChange(cond.id, $event)\"\n >\n @for (opt of col?.options ?? []; track $any(opt.value)) {\n <option [value]=\"opt.value\" [selected]=\"isSetValueSelected(cond, opt.value)\">\n {{ opt.label }}\n </option>\n }\n </select>\n } @else if (col?.filterType === 'boolean') {\n <select\n class=\"filter-builder__select\"\n [value]=\"cond.value.value === true ? 'true' : 'false'\"\n [attr.aria-label]=\"'Value for condition ' + (idx + 1)\"\n (change)=\"onValueChange(cond.id, { value: $any($event.target).value === 'true' })\"\n >\n <option value=\"true\">True</option>\n <option value=\"false\">False</option>\n </select>\n } @else {\n <input\n class=\"filter-builder__input\"\n [type]=\"inputTypeFor(cond.operator, col?.filterType ?? 'text')\"\n [value]=\"cond.value.value ?? ''\"\n [attr.aria-label]=\"'Value for condition ' + (idx + 1)\"\n (input)=\"onValueChange(cond.id, { value: $any($event.target).value })\"\n />\n @if (needsRange(cond.operator)) {\n <span class=\"filter-builder__range-sep\">\u2013</span>\n <input\n class=\"filter-builder__input\"\n [type]=\"inputTypeFor(cond.operator, col?.filterType ?? 'text')\"\n [value]=\"cond.value.valueTo ?? ''\"\n [attr.aria-label]=\"'Upper bound for condition ' + (idx + 1)\"\n (input)=\"onValueChange(cond.id, { valueTo: $any($event.target).value })\"\n />\n } } }\n </div>\n\n <!-- Delete -->\n <button\n type=\"button\"\n moz-button\n [ghost]=\"true\"\n [size]=\"'s'\"\n [iconPosition]=\"'only'\"\n [attr.aria-label]=\"'Remove condition ' + (idx + 1)\"\n (click)=\"removeCondition(cond.id)\"\n >\n <Cross20 icon />\n </button>\n\n <!-- Drag handle -->\n <span\n class=\"filter-builder__drag\"\n cdkDragHandle\n [attr.aria-label]=\"'Reorder condition ' + (idx + 1)\"\n >\n <Drag20 />\n </span>\n </div>\n }\n </div>\n\n <button\n type=\"button\"\n class=\"filter-builder__add\"\n [disabled]=\"availableColumns().length === 0\"\n (click)=\"addCondition()\"\n >\n <ListAdd20 />\n <span>Add condition</span>\n </button>\n</div>\n", styles: [":host{display:block;background:var(--Background-Primary, #fff);border:1px solid var(--Border-Primary, #cdd4d8);border-radius:6px;box-shadow:0 4px 16px #0000001f}.filter-builder{display:flex;flex-direction:column;gap:12px;padding:16px}.filter-builder__subtitle{margin:0;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--Text-Secondary, #555)}.filter-builder__rows{display:flex;flex-direction:column;gap:8px}.filter-builder__row{display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px solid var(--Border-Primary, #e0e0e0);border-radius:6px;background:var(--Background-Primary, #fff)}.filter-builder__row.cdk-drag-preview{box-shadow:0 2px 8px #0000001f}.filter-builder__row.cdk-drag-placeholder{opacity:.3}.filter-builder__combinator{min-width:64px;flex:0 0 64px}.filter-builder__combinator-label{display:inline-block;width:100%;padding:6px 8px;font-weight:600;color:var(--Text-Secondary, #555)}.filter-builder__select,.filter-builder__input{font:inherit;padding:6px 8px;border:1px solid var(--Border-Primary, #ccc);border-radius:4px;background:#fff;min-height:32px;box-sizing:border-box}.filter-builder__select--combinator{width:100%}.filter-builder__select--column,.filter-builder__select--operator{flex:1 1 120px;min-width:0}.filter-builder__select--set{min-width:160px;height:auto}.filter-builder__value{display:flex;align-items:center;gap:4px;flex:1 1 160px;min-width:0}.filter-builder__input{flex:1 1 0;min-width:0}.filter-builder__range-sep{color:var(--Text-Secondary, #777)}.filter-builder__drag{display:inline-flex;cursor:grab;color:var(--Text-Secondary, #777)}.filter-builder__drag:active{cursor:grabbing}.filter-builder__add{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;padding:6px 10px;background:transparent;border:none;color:var(--Status-Standalone-element-Primary, #0071ce);font:inherit;cursor:pointer;border-radius:4px}.filter-builder__add:hover:not([disabled]){background:var(--Background-Secondary, #f4f4f4)}.filter-builder__add[disabled]{cursor:not-allowed;opacity:.5}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: MozButtonComponent, selector: "button[moz-button]", inputs: ["appearance", "size", "disabled", "ghost", "outlined", "iconPosition", "type", "isLoading"] }, { kind: "component", type: Drag20, selector: "Drag20", inputs: ["hostClass"] }, { kind: "component", type: Cross20, selector: "Cross20", inputs: ["hostClass"] }, { kind: "component", type: ListAdd20, selector: "ListAdd20", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8702
+ }
8703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterBuilderComponent, decorators: [{
8704
+ type: Component,
8705
+ args: [{ selector: 'moz-grid-filter-builder', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
8706
+ FormsModule,
8707
+ CdkDropList,
8708
+ CdkDrag,
8709
+ CdkDragHandle,
8710
+ MozButtonComponent,
8711
+ Drag20,
8712
+ Cross20,
8713
+ ListAdd20,
8714
+ ], template: "<div class=\"filter-builder\">\n @if (showSubtitle()) {\n <p class=\"filter-builder__subtitle\">Show rows</p>\n }\n\n <div class=\"filter-builder__rows\" cdkDropList (cdkDropListDropped)=\"onDrop($event)\">\n @for (cond of draft(); track cond.id; let idx = $index; let first = $first) {\n <div class=\"filter-builder__row\" cdkDrag>\n <!-- Combinator -->\n <div class=\"filter-builder__combinator\">\n @if (first) {\n <span class=\"filter-builder__combinator-label\">Where</span>\n } @else {\n <select\n class=\"filter-builder__select filter-builder__select--combinator\"\n [value]=\"cond.combinator\"\n [attr.aria-label]=\"'Combinator for condition ' + (idx + 1)\"\n (change)=\"onCombinatorChange(cond.id, $any($event.target).value)\"\n >\n <option value=\"and\">And</option>\n <option value=\"or\">Or</option>\n </select>\n }\n </div>\n\n <!-- Column -->\n <select\n class=\"filter-builder__select filter-builder__select--column\"\n [value]=\"cond.field\"\n [attr.aria-label]=\"'Column for condition ' + (idx + 1)\"\n (change)=\"onFieldChange(cond.id, $any($event.target).value)\"\n >\n @for (col of availableColumns(); track col.field) {\n <option [value]=\"col.field\">{{ col.headerName }}</option>\n }\n </select>\n\n <!-- Operator -->\n <select\n class=\"filter-builder__select filter-builder__select--operator\"\n [value]=\"cond.operator\"\n [attr.aria-label]=\"'Operator for condition ' + (idx + 1)\"\n (change)=\"onOperatorChange(cond.id, $any($event.target).value)\"\n >\n @for (op of columnsById().get(cond.field)?.operators ?? []; track op) {\n <option [value]=\"op\">{{ operatorLabels[op] }}</option>\n }\n </select>\n\n <!-- Value -->\n <div class=\"filter-builder__value\">\n @if (needsValue(cond.operator)) { @let col = columnsById().get(cond.field); @if\n (col?.filterType === 'set') {\n <select\n class=\"filter-builder__select filter-builder__select--set\"\n multiple\n size=\"4\"\n [attr.aria-label]=\"'Values for condition ' + (idx + 1)\"\n (change)=\"onSetValueChange(cond.id, $event)\"\n >\n @for (opt of col?.options ?? []; track $any(opt.value)) {\n <option [value]=\"opt.value\" [selected]=\"isSetValueSelected(cond, opt.value)\">\n {{ opt.label }}\n </option>\n }\n </select>\n } @else if (col?.filterType === 'boolean') {\n <select\n class=\"filter-builder__select\"\n [value]=\"cond.value.value === true ? 'true' : 'false'\"\n [attr.aria-label]=\"'Value for condition ' + (idx + 1)\"\n (change)=\"onValueChange(cond.id, { value: $any($event.target).value === 'true' })\"\n >\n <option value=\"true\">True</option>\n <option value=\"false\">False</option>\n </select>\n } @else {\n <input\n class=\"filter-builder__input\"\n [type]=\"inputTypeFor(cond.operator, col?.filterType ?? 'text')\"\n [value]=\"cond.value.value ?? ''\"\n [attr.aria-label]=\"'Value for condition ' + (idx + 1)\"\n (input)=\"onValueChange(cond.id, { value: $any($event.target).value })\"\n />\n @if (needsRange(cond.operator)) {\n <span class=\"filter-builder__range-sep\">\u2013</span>\n <input\n class=\"filter-builder__input\"\n [type]=\"inputTypeFor(cond.operator, col?.filterType ?? 'text')\"\n [value]=\"cond.value.valueTo ?? ''\"\n [attr.aria-label]=\"'Upper bound for condition ' + (idx + 1)\"\n (input)=\"onValueChange(cond.id, { valueTo: $any($event.target).value })\"\n />\n } } }\n </div>\n\n <!-- Delete -->\n <button\n type=\"button\"\n moz-button\n [ghost]=\"true\"\n [size]=\"'s'\"\n [iconPosition]=\"'only'\"\n [attr.aria-label]=\"'Remove condition ' + (idx + 1)\"\n (click)=\"removeCondition(cond.id)\"\n >\n <Cross20 icon />\n </button>\n\n <!-- Drag handle -->\n <span\n class=\"filter-builder__drag\"\n cdkDragHandle\n [attr.aria-label]=\"'Reorder condition ' + (idx + 1)\"\n >\n <Drag20 />\n </span>\n </div>\n }\n </div>\n\n <button\n type=\"button\"\n class=\"filter-builder__add\"\n [disabled]=\"availableColumns().length === 0\"\n (click)=\"addCondition()\"\n >\n <ListAdd20 />\n <span>Add condition</span>\n </button>\n</div>\n", styles: [":host{display:block;background:var(--Background-Primary, #fff);border:1px solid var(--Border-Primary, #cdd4d8);border-radius:6px;box-shadow:0 4px 16px #0000001f}.filter-builder{display:flex;flex-direction:column;gap:12px;padding:16px}.filter-builder__subtitle{margin:0;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--Text-Secondary, #555)}.filter-builder__rows{display:flex;flex-direction:column;gap:8px}.filter-builder__row{display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px solid var(--Border-Primary, #e0e0e0);border-radius:6px;background:var(--Background-Primary, #fff)}.filter-builder__row.cdk-drag-preview{box-shadow:0 2px 8px #0000001f}.filter-builder__row.cdk-drag-placeholder{opacity:.3}.filter-builder__combinator{min-width:64px;flex:0 0 64px}.filter-builder__combinator-label{display:inline-block;width:100%;padding:6px 8px;font-weight:600;color:var(--Text-Secondary, #555)}.filter-builder__select,.filter-builder__input{font:inherit;padding:6px 8px;border:1px solid var(--Border-Primary, #ccc);border-radius:4px;background:#fff;min-height:32px;box-sizing:border-box}.filter-builder__select--combinator{width:100%}.filter-builder__select--column,.filter-builder__select--operator{flex:1 1 120px;min-width:0}.filter-builder__select--set{min-width:160px;height:auto}.filter-builder__value{display:flex;align-items:center;gap:4px;flex:1 1 160px;min-width:0}.filter-builder__input{flex:1 1 0;min-width:0}.filter-builder__range-sep{color:var(--Text-Secondary, #777)}.filter-builder__drag{display:inline-flex;cursor:grab;color:var(--Text-Secondary, #777)}.filter-builder__drag:active{cursor:grabbing}.filter-builder__add{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;padding:6px 10px;background:transparent;border:none;color:var(--Status-Standalone-element-Primary, #0071ce);font:inherit;cursor:pointer;border-radius:4px}.filter-builder__add:hover:not([disabled]){background:var(--Background-Secondary, #f4f4f4)}.filter-builder__add[disabled]{cursor:not-allowed;opacity:.5}\n"] }]
8715
+ }], ctorParameters: () => [], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }], availableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableColumns", required: true }] }], applyMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyMode", required: false }] }], showSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSubtitle", required: false }] }], modelChange: [{ type: i0.Output, args: ["modelChange"] }] } });
8716
+ function resetValueFor(op, previous) {
8717
+ if (VALUELESS_OPERATORS.has(op))
8718
+ return {};
8719
+ if (RANGE_OPERATORS.has(op))
8720
+ return { value: previous.value ?? '', valueTo: previous.valueTo ?? '' };
8721
+ return { value: previous.value ?? '' };
8722
+ }
8723
+ function conditionsEqual(a, b) {
8724
+ if (a.length !== b.length)
8725
+ return false;
8726
+ for (let i = 0; i < a.length; i++) {
8727
+ const ca = a[i];
8728
+ const cb = b[i];
8729
+ if (ca.id !== cb.id ||
8730
+ ca.combinator !== cb.combinator ||
8731
+ ca.field !== cb.field ||
8732
+ ca.operator !== cb.operator ||
8733
+ ca.value.value !== cb.value.value ||
8734
+ ca.value.valueTo !== cb.value.valueTo) {
8735
+ return false;
8736
+ }
8737
+ }
8738
+ return true;
8739
+ }
8740
+
8741
+ /**
8742
+ * Programmatically opens a CDK overlay anchored on the host element that
8743
+ * renders the filter builder. Unlike the action-listbox directive, the
8744
+ * overlay does not toggle on click — the host is simply the anchor. Open
8745
+ * the overlay by injecting this directive via a template ref (`#filter`)
8746
+ * and calling `filter.open(options)`.
8747
+ */
8748
+ class MozGridFilterOverlayDirective {
8749
+ overlay = inject(Overlay);
8750
+ host = inject((ElementRef));
8751
+ injector = inject(Injector);
8752
+ engine = inject(FilterEngine, { optional: true });
8753
+ overlayRef = null;
8754
+ componentRef = null;
8755
+ /** Opens the overlay anchored on the host. No-op if already open. */
8756
+ open(options) {
8757
+ if (this.overlayRef)
8758
+ return;
8759
+ if (!this.engine)
8760
+ return;
8761
+ const positions = [
8762
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
8763
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
8764
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
8765
+ ];
8766
+ const positionStrategy = this.overlay
8767
+ .position()
8768
+ .flexibleConnectedTo(this.host)
8769
+ .withPositions(positions)
8770
+ .withPush(true)
8771
+ .withViewportMargin(8);
8772
+ const config = new OverlayConfig({
8773
+ hasBackdrop: true,
8774
+ backdropClass: 'cdk-overlay-transparent-backdrop',
8775
+ panelClass: 'moz-grid-filter-overlay',
8776
+ positionStrategy,
8777
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
8778
+ });
8779
+ const overlayRef = this.overlay.create(config);
8780
+ this.overlayRef = overlayRef;
8781
+ // Seed the draft with a new condition targeting the clicked column, if any
8782
+ const seededModel = options.seedField && options.model.conditions.length === 0
8783
+ ? { conditions: [this.engine.makeCondition(options.seedField, true)] }
8784
+ : options.model;
8785
+ const portal = new ComponentPortal(MozGridFilterBuilderComponent, null, this.injector);
8786
+ const compRef = overlayRef.attach(portal);
8787
+ compRef.setInput('model', seededModel);
8788
+ compRef.setInput('availableColumns', options.columns);
8789
+ compRef.setInput('applyMode', 'auto');
8790
+ compRef.setInput('showSubtitle', true);
8791
+ compRef.instance.modelChange.subscribe((next) => {
8792
+ compRef.setInput('model', next);
8793
+ options.onChange(next);
8794
+ });
8795
+ this.componentRef = compRef;
8796
+ overlayRef.backdropClick().subscribe(() => this.close());
8797
+ overlayRef.keydownEvents().subscribe((event) => {
8798
+ if (event.key === 'Escape')
8799
+ this.close();
8800
+ });
8801
+ }
8802
+ close() {
8803
+ this.overlayRef?.dispose();
8804
+ this.overlayRef = null;
8805
+ this.componentRef = null;
8806
+ }
8807
+ ngOnDestroy() {
8808
+ this.close();
8809
+ }
8810
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8811
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: MozGridFilterOverlayDirective, isStandalone: true, selector: "[mozGridFilterOverlay]", exportAs: ["mozGridFilterOverlay"], ngImport: i0 });
8812
+ }
8813
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterOverlayDirective, decorators: [{
8814
+ type: Directive,
8815
+ args: [{
8816
+ selector: '[mozGridFilterOverlay]',
8817
+ exportAs: 'mozGridFilterOverlay',
8818
+ }]
8819
+ }] });
8820
+
8150
8821
  class MozGridHeaderCellComponent {
8151
8822
  state = inject(GridStateManager);
8823
+ filterEngine = inject(FilterEngine);
8152
8824
  columnState = input.required(...(ngDevMode ? [{ debugName: "columnState" }] : /* istanbul ignore next */ []));
8153
8825
  def = input.required(...(ngDevMode ? [{ debugName: "def" }] : /* istanbul ignore next */ []));
8154
8826
  isLast = input(false, ...(ngDevMode ? [{ debugName: "isLast" }] : /* istanbul ignore next */ []));
8155
8827
  pinnedEnd = input(false, ...(ngDevMode ? [{ debugName: "pinnedEnd" }] : /* istanbul ignore next */ []));
8156
8828
  reorderable = input(false, ...(ngDevMode ? [{ debugName: "reorderable" }] : /* istanbul ignore next */ []));
8829
+ filterOverlay = viewChild(MozGridFilterOverlayDirective, ...(ngDevMode ? [{ debugName: "filterOverlay" }] : /* istanbul ignore next */ []));
8830
+ /** True when at least one active condition targets this column. */
8831
+ hasActiveFilter = computed(() => this.filterEngine.conditions().some((c) => c.field === this.columnState().field), ...(ngDevMode ? [{ debugName: "hasActiveFilter" }] : /* istanbul ignore next */ []));
8832
+ /** Tooltip for the gear / filter button (count + short summary). */
8833
+ filterTooltip = computed(() => {
8834
+ const field = this.columnState().field;
8835
+ const matching = this.filterEngine.conditions().filter((c) => c.field === field);
8836
+ if (matching.length === 0)
8837
+ return 'Column settings';
8838
+ const summary = matching.map((c) => this.filterEngine.toLabel(c)).join(', ');
8839
+ return `${matching.length} filter${matching.length > 1 ? 's' : ''}: ${summary}`;
8840
+ }, ...(ngDevMode ? [{ debugName: "filterTooltip" }] : /* istanbul ignore next */ []));
8157
8841
  resolvedMinWidth = computed(() => {
8158
8842
  const def = this.def();
8159
8843
  return def.minWidth ? parseInt(def.minWidth, 10) || 50 : 50;
@@ -8175,6 +8859,14 @@ class MozGridHeaderCellComponent {
8175
8859
  if (def.sortable !== false) {
8176
8860
  items.push({ id: 'sort-asc', icon: ChevronUp20, label: 'Sort A → Z' }, { id: 'sort-desc', icon: ChevronDown20, label: 'Sort Z → A' });
8177
8861
  }
8862
+ if (def.filterable) {
8863
+ items.push({
8864
+ id: 'filter-column',
8865
+ label: 'Filter in this column',
8866
+ icon: Filter20,
8867
+ divider: items.length > 0,
8868
+ });
8869
+ }
8178
8870
  if (def.groupable) {
8179
8871
  items.push({
8180
8872
  id: 'group-column',
@@ -8220,24 +8912,58 @@ class MozGridHeaderCellComponent {
8220
8912
  }
8221
8913
  }
8222
8914
  onMenuItemClick(item) {
8223
- this.menuAction.emit({
8224
- field: this.columnState().field,
8225
- actionId: item.id,
8226
- });
8915
+ const field = this.columnState().field;
8916
+ const actionId = item.id;
8917
+ // Intercept "Filter in this column": when the column does not provide a
8918
+ // custom `filterTemplate`, open the overlay anchored on the gear button
8919
+ // instead of delegating to the grid shell (which would toggle the legacy
8920
+ // per-column search input). Columns that ship a filterTemplate keep their
8921
+ // legacy behaviour — the grid handles them via `onMenuAction`.
8922
+ if (actionId === 'filter-column' && !this.def().filterTemplate) {
8923
+ this.openFilterOverlay(field);
8924
+ return;
8925
+ }
8926
+ this.menuAction.emit({ field, actionId });
8927
+ }
8928
+ openFilterOverlay(seedField) {
8929
+ // Defer so the action-listbox has time to tear down its overlay before
8930
+ // we attach a new one at roughly the same location. Without this, the
8931
+ // lingering mouseup from the menu-item click can land on the freshly
8932
+ // mounted filter backdrop and close it instantly.
8933
+ setTimeout(() => {
8934
+ const overlay = this.filterOverlay();
8935
+ if (!overlay)
8936
+ return;
8937
+ overlay.open({
8938
+ columns: this.filterEngine.describeFilterableColumns(),
8939
+ model: { conditions: this.filterEngine.conditions().slice() },
8940
+ seedField,
8941
+ onChange: (next) => {
8942
+ this.filterEngine.setModel(next, 'replace');
8943
+ },
8944
+ });
8945
+ }, 0);
8227
8946
  }
8228
8947
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridHeaderCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8229
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridHeaderCellComponent, isStandalone: true, selector: "moz-grid-header-cell", inputs: { columnState: { classPropertyName: "columnState", publicName: "columnState", isSignal: true, isRequired: true, transformFunction: null }, def: { classPropertyName: "def", publicName: "def", isSignal: true, isRequired: true, transformFunction: null }, isLast: { classPropertyName: "isLast", publicName: "isLast", isSignal: true, isRequired: false, transformFunction: null }, pinnedEnd: { classPropertyName: "pinnedEnd", publicName: "pinnedEnd", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortClick: "sortClick", menuAction: "menuAction", resizeStart: "resizeStart" }, host: { properties: { "style.flex": "isLast() ? \"1 0 auto\" : \"0 0 auto\"", "style.width.px": "isLast() ? undefined : columnState().currentWidth", "style.min-width.px": "isLast() ? columnState().currentWidth : resolvedMinWidth()", "class.grid-header-cell-host--dragging": "isDragging()", "class.grid-header-cell-host--reorderable": "reorderable()" } }, ngImport: i0, template: "<div\n class=\"grid-header-cell\"\n [class.grid-header-cell--sorted]=\"columnState().sort !== null\"\n [class.grid-header-cell--last]=\"isLast()\"\n [class.grid-header-cell--pinned-end]=\"pinnedEnd()\"\n>\n <div class=\"grid-header-cell__content\" (click)=\"onHeaderClick($event)\">\n <span class=\"grid-header-cell__label\">{{ label() }}</span>\n @if (columnState().sort === 'asc') {\n <ChevronUp20 class=\"grid-header-cell__sort-icon\" />\n } @if (columnState().sort === 'desc') {\n <ChevronDown20 class=\"grid-header-cell__sort-icon\" />\n }\n </div>\n\n @if (!def().headerMenuDisabled) {\n <button\n type=\"button\"\n class=\"grid-header-cell__menu-trigger\"\n [attr.aria-label]=\"'Column settings for ' + label()\"\n [mozActionListboxTrigger]=\"menuItems()\"\n actionListboxPosition=\"below\"\n (itemClick)=\"onMenuItemClick($event)\"\n (click)=\"$event.stopPropagation()\"\n >\n <Settings20 />\n </button>\n } @if (def().resizable !== false && (!isLast() || pinnedEnd())) {\n <div\n class=\"grid-header-cell__resize-handle\"\n [class.grid-header-cell__resize-handle--left]=\"pinnedEnd()\"\n (mousedown)=\"resizeStart.emit($event)\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;height:100%}.grid-header-cell{display:flex;align-items:center;position:relative;height:100%;padding:0 var(--spacing-s, 8px);border-right:1px solid var(--color-border-primary);-webkit-user-select:none;user-select:none;box-sizing:border-box;min-width:0;background:inherit}.grid-header-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-header-cell--pinned-end{border-left:none}.grid-header-cell--last{border-right:none}.grid-header-cell__content{display:flex;align-items:center;gap:var(--spacing-xs, 4px);flex:1;min-width:0;cursor:pointer}.grid-header-cell__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-secondary);font-family:LeroyMerlin,sans-serif;font-size:var(--font-size-50);font-weight:700;text-transform:uppercase}.grid-header-cell__sort-icon{flex-shrink:0;color:var(--color-text-primary)}.grid-header-cell__menu-trigger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:28px;height:28px;padding:0;border:none;border-radius:var(--border-radius-s, 4px);background:transparent;cursor:pointer;color:var(--color-text-secondary);opacity:0;transition:opacity .15s ease}.grid-header-cell__menu-trigger:hover{background:var(--color-background-tertiary, rgba(0, 0, 0, .06));color:var(--color-text-primary)}.grid-header-cell:hover .grid-header-cell__menu-trigger{opacity:1}.grid-header-cell__resize-handle{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;z-index:1}.grid-header-cell__resize-handle:hover{background:var(--color-background-accent-inverse)}.grid-header-cell__resize-handle--left{right:auto;left:0}:host(.grid-header-cell-host--reorderable) .grid-header-cell__content{cursor:grab}:host(.grid-header-cell-host--dragging){outline:2px solid var(--color-background-accent-inverse);outline-offset:-2px;opacity:.85;z-index:1}\n"], dependencies: [{ kind: "component", type: ChevronDown20, selector: "ChevronDown20", inputs: ["hostClass"] }, { kind: "component", type: ChevronUp20, selector: "ChevronUp20", inputs: ["hostClass"] }, { kind: "component", type: Settings20, selector: "Settings20", inputs: ["hostClass"] }, { kind: "directive", type: MozActionListboxTriggerDirective, selector: "[mozActionListboxTrigger]", inputs: ["mozActionListboxTrigger", "actionListboxTitle", "actionListboxPosition"], outputs: ["itemClick"], exportAs: ["mozActionListboxTrigger"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8948
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridHeaderCellComponent, isStandalone: true, selector: "moz-grid-header-cell", inputs: { columnState: { classPropertyName: "columnState", publicName: "columnState", isSignal: true, isRequired: true, transformFunction: null }, def: { classPropertyName: "def", publicName: "def", isSignal: true, isRequired: true, transformFunction: null }, isLast: { classPropertyName: "isLast", publicName: "isLast", isSignal: true, isRequired: false, transformFunction: null }, pinnedEnd: { classPropertyName: "pinnedEnd", publicName: "pinnedEnd", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortClick: "sortClick", menuAction: "menuAction", resizeStart: "resizeStart" }, host: { properties: { "style.flex": "isLast() ? \"1 0 auto\" : \"0 0 auto\"", "style.width.px": "isLast() ? undefined : columnState().currentWidth", "style.min-width.px": "isLast() ? columnState().currentWidth : resolvedMinWidth()", "class.grid-header-cell-host--dragging": "isDragging()", "class.grid-header-cell-host--reorderable": "reorderable()" } }, viewQueries: [{ propertyName: "filterOverlay", first: true, predicate: MozGridFilterOverlayDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"grid-header-cell\"\n [class.grid-header-cell--sorted]=\"columnState().sort !== null\"\n [class.grid-header-cell--last]=\"isLast()\"\n [class.grid-header-cell--pinned-end]=\"pinnedEnd()\"\n>\n <div class=\"grid-header-cell__content\" (click)=\"onHeaderClick($event)\">\n <span class=\"grid-header-cell__label\">{{ label() }}</span>\n @if (columnState().sort === 'asc') {\n <ChevronUp20 class=\"grid-header-cell__sort-icon\" />\n } @if (columnState().sort === 'desc') {\n <ChevronDown20 class=\"grid-header-cell__sort-icon\" />\n }\n </div>\n\n @if (!def().headerMenuDisabled) {\n <button\n type=\"button\"\n class=\"grid-header-cell__menu-trigger\"\n [class.grid-header-cell__menu-trigger--filtered]=\"hasActiveFilter()\"\n [attr.aria-label]=\"'Column settings for ' + label()\"\n [attr.title]=\"filterTooltip()\"\n [mozActionListboxTrigger]=\"menuItems()\"\n actionListboxPosition=\"below\"\n mozGridFilterOverlay\n (itemClick)=\"onMenuItemClick($event)\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (hasActiveFilter()) {\n <Filter20 />\n } @else {\n <Settings20 />\n }\n </button>\n } @if (def().resizable !== false && (!isLast() || pinnedEnd())) {\n <div\n class=\"grid-header-cell__resize-handle\"\n [class.grid-header-cell__resize-handle--left]=\"pinnedEnd()\"\n (mousedown)=\"resizeStart.emit($event)\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;height:100%}.grid-header-cell{display:flex;align-items:center;position:relative;height:100%;padding:0 var(--spacing-s, 8px);border-right:1px solid var(--color-border-primary);-webkit-user-select:none;user-select:none;box-sizing:border-box;min-width:0;background:inherit}.grid-header-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-header-cell--pinned-end{border-left:none}.grid-header-cell--last{border-right:none}.grid-header-cell__content{display:flex;align-items:center;gap:var(--spacing-xs, 4px);flex:1;min-width:0;cursor:pointer}.grid-header-cell__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-secondary);font-family:LeroyMerlin,sans-serif;font-size:var(--font-size-50);font-weight:700;text-transform:uppercase}.grid-header-cell__sort-icon{flex-shrink:0;color:var(--color-text-primary)}.grid-header-cell__menu-trigger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:28px;height:28px;padding:0;border:none;border-radius:var(--border-radius-s, 4px);background:transparent;cursor:pointer;color:var(--color-text-secondary);opacity:0;transition:opacity .15s ease}.grid-header-cell__menu-trigger:hover{background:var(--color-background-tertiary, rgba(0, 0, 0, .06));color:var(--color-text-primary)}.grid-header-cell:hover .grid-header-cell__menu-trigger{opacity:1}.grid-header-cell__menu-trigger--filtered{opacity:1;color:var(--Status-Standalone-element-Primary, #0071ce)}.grid-header-cell__resize-handle{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;z-index:1}.grid-header-cell__resize-handle:hover{background:var(--color-background-accent-inverse)}.grid-header-cell__resize-handle--left{right:auto;left:0}:host(.grid-header-cell-host--reorderable) .grid-header-cell__content{cursor:grab}:host(.grid-header-cell-host--dragging){outline:2px solid var(--color-background-accent-inverse);outline-offset:-2px;opacity:.85;z-index:1}\n"], dependencies: [{ kind: "component", type: ChevronDown20, selector: "ChevronDown20", inputs: ["hostClass"] }, { kind: "component", type: ChevronUp20, selector: "ChevronUp20", inputs: ["hostClass"] }, { kind: "component", type: Settings20, selector: "Settings20", inputs: ["hostClass"] }, { kind: "component", type: Filter20, selector: "Filter20", inputs: ["hostClass"] }, { kind: "directive", type: MozActionListboxTriggerDirective, selector: "[mozActionListboxTrigger]", inputs: ["mozActionListboxTrigger", "actionListboxTitle", "actionListboxPosition"], outputs: ["itemClick"], exportAs: ["mozActionListboxTrigger"] }, { kind: "directive", type: MozGridFilterOverlayDirective, selector: "[mozGridFilterOverlay]", exportAs: ["mozGridFilterOverlay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8230
8949
  }
8231
8950
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridHeaderCellComponent, decorators: [{
8232
8951
  type: Component,
8233
- args: [{ selector: 'moz-grid-header-cell', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChevronDown20, ChevronUp20, Settings20, MozActionListboxTriggerDirective], host: {
8952
+ args: [{ selector: 'moz-grid-header-cell', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
8953
+ ChevronDown20,
8954
+ ChevronUp20,
8955
+ Settings20,
8956
+ Filter20,
8957
+ MozActionListboxTriggerDirective,
8958
+ MozGridFilterOverlayDirective,
8959
+ ], host: {
8234
8960
  '[style.flex]': 'isLast() ? "1 0 auto" : "0 0 auto"',
8235
8961
  '[style.width.px]': 'isLast() ? undefined : columnState().currentWidth',
8236
8962
  '[style.min-width.px]': 'isLast() ? columnState().currentWidth : resolvedMinWidth()',
8237
8963
  '[class.grid-header-cell-host--dragging]': 'isDragging()',
8238
8964
  '[class.grid-header-cell-host--reorderable]': 'reorderable()',
8239
- }, template: "<div\n class=\"grid-header-cell\"\n [class.grid-header-cell--sorted]=\"columnState().sort !== null\"\n [class.grid-header-cell--last]=\"isLast()\"\n [class.grid-header-cell--pinned-end]=\"pinnedEnd()\"\n>\n <div class=\"grid-header-cell__content\" (click)=\"onHeaderClick($event)\">\n <span class=\"grid-header-cell__label\">{{ label() }}</span>\n @if (columnState().sort === 'asc') {\n <ChevronUp20 class=\"grid-header-cell__sort-icon\" />\n } @if (columnState().sort === 'desc') {\n <ChevronDown20 class=\"grid-header-cell__sort-icon\" />\n }\n </div>\n\n @if (!def().headerMenuDisabled) {\n <button\n type=\"button\"\n class=\"grid-header-cell__menu-trigger\"\n [attr.aria-label]=\"'Column settings for ' + label()\"\n [mozActionListboxTrigger]=\"menuItems()\"\n actionListboxPosition=\"below\"\n (itemClick)=\"onMenuItemClick($event)\"\n (click)=\"$event.stopPropagation()\"\n >\n <Settings20 />\n </button>\n } @if (def().resizable !== false && (!isLast() || pinnedEnd())) {\n <div\n class=\"grid-header-cell__resize-handle\"\n [class.grid-header-cell__resize-handle--left]=\"pinnedEnd()\"\n (mousedown)=\"resizeStart.emit($event)\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;height:100%}.grid-header-cell{display:flex;align-items:center;position:relative;height:100%;padding:0 var(--spacing-s, 8px);border-right:1px solid var(--color-border-primary);-webkit-user-select:none;user-select:none;box-sizing:border-box;min-width:0;background:inherit}.grid-header-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-header-cell--pinned-end{border-left:none}.grid-header-cell--last{border-right:none}.grid-header-cell__content{display:flex;align-items:center;gap:var(--spacing-xs, 4px);flex:1;min-width:0;cursor:pointer}.grid-header-cell__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-secondary);font-family:LeroyMerlin,sans-serif;font-size:var(--font-size-50);font-weight:700;text-transform:uppercase}.grid-header-cell__sort-icon{flex-shrink:0;color:var(--color-text-primary)}.grid-header-cell__menu-trigger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:28px;height:28px;padding:0;border:none;border-radius:var(--border-radius-s, 4px);background:transparent;cursor:pointer;color:var(--color-text-secondary);opacity:0;transition:opacity .15s ease}.grid-header-cell__menu-trigger:hover{background:var(--color-background-tertiary, rgba(0, 0, 0, .06));color:var(--color-text-primary)}.grid-header-cell:hover .grid-header-cell__menu-trigger{opacity:1}.grid-header-cell__resize-handle{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;z-index:1}.grid-header-cell__resize-handle:hover{background:var(--color-background-accent-inverse)}.grid-header-cell__resize-handle--left{right:auto;left:0}:host(.grid-header-cell-host--reorderable) .grid-header-cell__content{cursor:grab}:host(.grid-header-cell-host--dragging){outline:2px solid var(--color-background-accent-inverse);outline-offset:-2px;opacity:.85;z-index:1}\n"] }]
8240
- }], propDecorators: { columnState: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnState", required: true }] }], def: [{ type: i0.Input, args: [{ isSignal: true, alias: "def", required: true }] }], isLast: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLast", required: false }] }], pinnedEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedEnd", required: false }] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], sortClick: [{ type: i0.Output, args: ["sortClick"] }], menuAction: [{ type: i0.Output, args: ["menuAction"] }], resizeStart: [{ type: i0.Output, args: ["resizeStart"] }] } });
8965
+ }, template: "<div\n class=\"grid-header-cell\"\n [class.grid-header-cell--sorted]=\"columnState().sort !== null\"\n [class.grid-header-cell--last]=\"isLast()\"\n [class.grid-header-cell--pinned-end]=\"pinnedEnd()\"\n>\n <div class=\"grid-header-cell__content\" (click)=\"onHeaderClick($event)\">\n <span class=\"grid-header-cell__label\">{{ label() }}</span>\n @if (columnState().sort === 'asc') {\n <ChevronUp20 class=\"grid-header-cell__sort-icon\" />\n } @if (columnState().sort === 'desc') {\n <ChevronDown20 class=\"grid-header-cell__sort-icon\" />\n }\n </div>\n\n @if (!def().headerMenuDisabled) {\n <button\n type=\"button\"\n class=\"grid-header-cell__menu-trigger\"\n [class.grid-header-cell__menu-trigger--filtered]=\"hasActiveFilter()\"\n [attr.aria-label]=\"'Column settings for ' + label()\"\n [attr.title]=\"filterTooltip()\"\n [mozActionListboxTrigger]=\"menuItems()\"\n actionListboxPosition=\"below\"\n mozGridFilterOverlay\n (itemClick)=\"onMenuItemClick($event)\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (hasActiveFilter()) {\n <Filter20 />\n } @else {\n <Settings20 />\n }\n </button>\n } @if (def().resizable !== false && (!isLast() || pinnedEnd())) {\n <div\n class=\"grid-header-cell__resize-handle\"\n [class.grid-header-cell__resize-handle--left]=\"pinnedEnd()\"\n (mousedown)=\"resizeStart.emit($event)\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;height:100%}.grid-header-cell{display:flex;align-items:center;position:relative;height:100%;padding:0 var(--spacing-s, 8px);border-right:1px solid var(--color-border-primary);-webkit-user-select:none;user-select:none;box-sizing:border-box;min-width:0;background:inherit}.grid-header-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-header-cell--pinned-end{border-left:none}.grid-header-cell--last{border-right:none}.grid-header-cell__content{display:flex;align-items:center;gap:var(--spacing-xs, 4px);flex:1;min-width:0;cursor:pointer}.grid-header-cell__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-secondary);font-family:LeroyMerlin,sans-serif;font-size:var(--font-size-50);font-weight:700;text-transform:uppercase}.grid-header-cell__sort-icon{flex-shrink:0;color:var(--color-text-primary)}.grid-header-cell__menu-trigger{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:28px;height:28px;padding:0;border:none;border-radius:var(--border-radius-s, 4px);background:transparent;cursor:pointer;color:var(--color-text-secondary);opacity:0;transition:opacity .15s ease}.grid-header-cell__menu-trigger:hover{background:var(--color-background-tertiary, rgba(0, 0, 0, .06));color:var(--color-text-primary)}.grid-header-cell:hover .grid-header-cell__menu-trigger{opacity:1}.grid-header-cell__menu-trigger--filtered{opacity:1;color:var(--Status-Standalone-element-Primary, #0071ce)}.grid-header-cell__resize-handle{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;z-index:1}.grid-header-cell__resize-handle:hover{background:var(--color-background-accent-inverse)}.grid-header-cell__resize-handle--left{right:auto;left:0}:host(.grid-header-cell-host--reorderable) .grid-header-cell__content{cursor:grab}:host(.grid-header-cell-host--dragging){outline:2px solid var(--color-background-accent-inverse);outline-offset:-2px;opacity:.85;z-index:1}\n"] }]
8966
+ }], propDecorators: { columnState: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnState", required: true }] }], def: [{ type: i0.Input, args: [{ isSignal: true, alias: "def", required: true }] }], isLast: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLast", required: false }] }], pinnedEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedEnd", required: false }] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], filterOverlay: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MozGridFilterOverlayDirective), { isSignal: true }] }], sortClick: [{ type: i0.Output, args: ["sortClick"] }], menuAction: [{ type: i0.Output, args: ["menuAction"] }], resizeStart: [{ type: i0.Output, args: ["resizeStart"] }] } });
8241
8967
 
8242
8968
  function trackDisplayRow(_index, row) {
8243
8969
  if (row.type === 'group') {
@@ -8490,6 +9216,12 @@ class AutoSizeVirtualScrollStrategy {
8490
9216
  const viewportSize = this._viewport.getViewportSize();
8491
9217
  const dataLength = this._viewport.getDataLength();
8492
9218
  if (dataLength === 0) {
9219
+ // IMPORTANT: also reset the cache. Otherwise, when data comes back to
9220
+ // the same length as before the empty state (e.g. filter matching N,
9221
+ // then 0, then N again), the range we compute below equals the stale
9222
+ // `_lastRenderedRange` and `setRenderedRange` is skipped — the viewport
9223
+ // stays stuck on empty until a scroll event forces a re-render.
9224
+ this._lastRenderedRange = { start: 0, end: 0 };
8493
9225
  this._viewport.setRenderedRange({ start: 0, end: 0 });
8494
9226
  this._viewport.setRenderedContentOffset(0);
8495
9227
  return;
@@ -8637,8 +9369,9 @@ class AutoSizeVirtualScrollStrategy {
8637
9369
  // Quick check: if the first child matches the default height,
8638
9370
  // assume all rows are uniform and skip the full measurement pass.
8639
9371
  const firstHeight = children[0].offsetHeight;
8640
- if (firstHeight > 0 && Math.abs(firstHeight - this._defaultItemSize) <= 0.5
8641
- && this._heightMap.size === 0) {
9372
+ if (firstHeight > 0 &&
9373
+ Math.abs(firstHeight - this._defaultItemSize) <= 0.5 &&
9374
+ this._heightMap.size === 0) {
8642
9375
  return;
8643
9376
  }
8644
9377
  let changed = false;
@@ -8765,13 +9498,36 @@ class MozGridCellComponent {
8765
9498
  const editorEl = this.elRef.nativeElement.querySelector('.grid-cell__editor');
8766
9499
  if (!editorEl)
8767
9500
  return;
8768
- // Temporarily remove overflow constraint to measure natural content width
8769
9501
  const cell = this.elRef.nativeElement.querySelector('.grid-cell');
9502
+ // The cell-level `overflow: hidden`, the editor's `width: 100%`, the
9503
+ // `flex-wrap: wrap` on `.grid-cell__editor-custom` and the global
9504
+ // `.grid-cell__editor ::ng-deep * { max-width: 100% }` rule all collapse
9505
+ // the editor's content back inside the current column width. Lift each of
9506
+ // those — on the editor and every descendant — so scrollWidth reports
9507
+ // the true natural width. Restore everything after measuring.
9508
+ const prevCellOverflow = cell?.style.overflow ?? '';
9509
+ const prevEditorCss = editorEl.style.cssText;
9510
+ const descendants = editorEl.querySelectorAll('*');
9511
+ const prevDescendantCss = [];
9512
+ descendants.forEach((el) => {
9513
+ prevDescendantCss.push(el.style.cssText);
9514
+ el.style.maxWidth = 'none';
9515
+ el.style.minWidth = 'max-content';
9516
+ el.style.flexWrap = 'nowrap';
9517
+ el.style.overflow = 'visible';
9518
+ });
8770
9519
  if (cell)
8771
9520
  cell.style.overflow = 'visible';
9521
+ editorEl.style.width = 'max-content';
9522
+ editorEl.style.maxWidth = 'none';
9523
+ editorEl.style.overflow = 'visible';
8772
9524
  const contentWidth = editorEl.scrollWidth + 16; // 16 = cell horizontal padding (8px × 2)
8773
9525
  if (cell)
8774
- cell.style.overflow = '';
9526
+ cell.style.overflow = prevCellOverflow;
9527
+ editorEl.style.cssText = prevEditorCss;
9528
+ descendants.forEach((el, i) => {
9529
+ el.style.cssText = prevDescendantCss[i];
9530
+ });
8775
9531
  if (contentWidth > currentWidth) {
8776
9532
  this.state.updateColumnState(field, { currentWidth: contentWidth });
8777
9533
  }
@@ -8950,7 +9706,7 @@ class MozGridCellComponent {
8950
9706
  });
8951
9707
  }
8952
9708
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8953
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridCellComponent, isStandalone: true, selector: "moz-grid-cell", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, colState: { classPropertyName: "colState", publicName: "colState", isSignal: true, isRequired: true, transformFunction: null }, def: { classPropertyName: "def", publicName: "def", isSignal: true, isRequired: true, transformFunction: null }, isLast: { classPropertyName: "isLast", publicName: "isLast", isSignal: true, isRequired: false, transformFunction: null }, pinnedEnd: { classPropertyName: "pinnedEnd", publicName: "pinnedEnd", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commitEdit: "commitEdit", cancelEdit: "cancelEdit" }, host: { properties: { "style.flex": "isLast() ? \"1 0 auto\" : \"0 0 auto\"", "style.width.px": "isLast() ? undefined : colState().currentWidth", "style.min-width.px": "isLast() ? colState().currentWidth : resolvedMinWidth()" } }, ngImport: i0, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--cut]=\"cutEdges().any\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (cutEdges(); as edges) { @if (edges.top) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--top\"></div>\n } @if (edges.bottom) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--bottom\"></div>\n } @if (edges.left) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--left\"></div>\n } @if (edges.right) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--right\"></div>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;min-width:0}.grid-cell{display:flex;align-items:center;position:relative;padding:0 var(--spacing-s, 8px);height:100%;border-right:1px solid var(--color-border-primary);overflow:hidden;box-sizing:border-box;min-width:0;background:inherit;cursor:pointer}.grid-cell--last{border-right:none}.grid-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-cell--pinned-end{border-left:none}.grid-cell__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);position:relative;z-index:1}.grid-cell__editor{width:100%;min-width:0;max-width:100%;height:100%;display:flex;align-items:center;overflow:hidden;box-sizing:border-box;position:relative;z-index:1}.grid-cell__editor-custom{width:100%;min-width:0;max-width:100%;overflow:hidden;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;gap:4px}.grid-cell__editor input,.grid-cell__editor moz-select{width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain{border:none;outline:none;background:transparent;font-family:inherit;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);padding:0;height:100%;width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain:focus{outline:none}.grid-cell__input--plain[type=number]::-webkit-inner-spin-button,.grid-cell__input--plain[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.grid-cell__input--plain[type=number]{-moz-appearance:textfield}.grid-cell__editor ::ng-deep .text-input{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .select,.grid-cell__editor ::ng-deep .select__trigger{min-width:0;width:100%}.grid-cell__editor ::ng-deep *{max-width:100%}.grid-cell__editor moz-datepicker{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .mc-datepicker,.grid-cell__editor ::ng-deep .mc-text-input{width:100%;min-width:0;box-sizing:border-box;height:28px;font-size:var(--font-size-xs, 12px)}.grid-cell__editor ::ng-deep moz-datepicker label{display:none}.grid-cell>*:not(.grid-cell__fill-handle){position:relative;z-index:1}.grid-cell:before{content:\"\";position:absolute;inset:3px;border-radius:4px;pointer-events:none;z-index:0}.grid-cell:hover:before{background:#f1f3f4}.grid-cell--focused:hover:before,.grid-cell--in-range:hover:before,.grid-cell--in-fill-range:hover:before,.grid-cell--in-fill-reject-range:hover:before{background:transparent}.grid-cell--focused{z-index:2}.grid-cell--focused:after{content:\"\";position:absolute;inset:0;border:2px solid var(--color-background-accent-inverse);border-radius:4px;pointer-events:none}.grid-cell--in-range{background:var(--color-background-accent)}.grid-cell--readonly .grid-cell__value{color:var(--color-text-secondary, #666)}.grid-cell--in-fill-range{background:#34a85314}.grid-cell--in-fill-range:after{content:\"\";position:absolute;inset:0;border:1px dashed var(--color-success, #34a853);border-radius:4px;pointer-events:none}.grid-cell--in-fill-reject-range{background:#ea302d1f;cursor:not-allowed;z-index:2}.grid-cell--in-fill-reject-range:after{content:\"\";position:absolute;inset:0;border:2px dashed var(--Status-Standalone-element-Error, #ea302d);border-radius:4px;pointer-events:none;z-index:3}.grid-cell--cut{background:#1a73e80f}.grid-cell__cut-mark{position:absolute;pointer-events:none;z-index:5;--cut-color: var(--color-background-accent-inverse, #1a73e8)}.grid-cell__cut-mark--top,.grid-cell__cut-mark--bottom{left:0;right:0;height:2px;background-image:linear-gradient(90deg,var(--cut-color) 50%,transparent 50%);background-size:8px 2px;background-repeat:repeat-x;animation:moz-grid-marching-ants-x .5s linear infinite}.grid-cell__cut-mark--top{top:0}.grid-cell__cut-mark--bottom{bottom:0}.grid-cell__cut-mark--left,.grid-cell__cut-mark--right{top:0;bottom:0;width:2px;background-image:linear-gradient(180deg,var(--cut-color) 50%,transparent 50%);background-size:2px 8px;background-repeat:repeat-y;animation:moz-grid-marching-ants-y .5s linear infinite}.grid-cell__cut-mark--left{left:0}.grid-cell__cut-mark--right{right:0}@keyframes moz-grid-marching-ants-x{0%{background-position-x:0}to{background-position-x:8px}}@keyframes moz-grid-marching-ants-y{0%{background-position-y:0}to{background-position-y:8px}}.grid-cell__fill-handle{position:absolute;right:0;bottom:0;width:8px;height:8px;background:var(--color-background-accent-inverse);cursor:crosshair;z-index:4}.grid-cell--error{background:var(--Background-Primary, #fff);outline:1px solid var(--Status-Border-Error, #ef5f5c);outline-offset:-2px;z-index:1;border-radius:4px}.grid-cell--error:hover:before{background:transparent}.grid-cell__error-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;cursor:help;position:relative;z-index:2}.grid-cell__error-icon ::ng-deep svg{fill:var(--Status-Standalone-element-Error, #ea302d)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MozSelectComponent, selector: "moz-select", inputs: ["id", "name", "options", "placeholder", "isInvalid", "disabled", "readonly", "size"] }, { kind: "component", type: MozCheckboxComponent, selector: "moz-checkbox", inputs: ["id", "name", "label", "indeterminate", "isInvalid", "disabled", "indented"] }, { kind: "component", type: MozDatepickerComponent, selector: "moz-datepicker", inputs: ["id", "disabled", "readonly", "invalid", "error", "clearable", "size", "label"] }, { kind: "directive", type: MozTooltipDirective, selector: "[mozTooltip]", inputs: ["mozTooltip", "tooltipPosition", "tooltipNoPointer"] }, { kind: "component", type: ErrorFilled24, selector: "ErrorFilled24", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9709
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridCellComponent, isStandalone: true, selector: "moz-grid-cell", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, colState: { classPropertyName: "colState", publicName: "colState", isSignal: true, isRequired: true, transformFunction: null }, def: { classPropertyName: "def", publicName: "def", isSignal: true, isRequired: true, transformFunction: null }, isLast: { classPropertyName: "isLast", publicName: "isLast", isSignal: true, isRequired: false, transformFunction: null }, pinnedEnd: { classPropertyName: "pinnedEnd", publicName: "pinnedEnd", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commitEdit: "commitEdit", cancelEdit: "cancelEdit" }, host: { properties: { "style.flex": "isLast() ? \"1 0 auto\" : \"0 0 auto\"", "style.width.px": "isLast() ? undefined : colState().currentWidth", "style.min-width.px": "isLast() ? colState().currentWidth : resolvedMinWidth()" } }, ngImport: i0, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--cut]=\"cutEdges().any\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (cutEdges(); as edges) { @if (edges.top) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--top\"></div>\n } @if (edges.bottom) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--bottom\"></div>\n } @if (edges.left) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--left\"></div>\n } @if (edges.right) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--right\"></div>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;min-width:0}.grid-cell{display:flex;align-items:center;position:relative;padding:0 var(--spacing-s, 8px);height:100%;border-right:1px solid var(--color-border-primary);overflow:hidden;box-sizing:border-box;min-width:0;background:inherit;cursor:pointer}.grid-cell--last{border-right:none}.grid-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-cell--pinned-end{border-left:none}.grid-cell__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);position:relative;z-index:1}.grid-cell__editor{width:100%;min-width:0;max-width:100%;height:100%;display:flex;align-items:center;overflow:hidden;box-sizing:border-box;position:relative;z-index:1}.grid-cell__editor-custom{width:100%;min-width:0;max-width:100%;overflow:hidden;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;gap:4px}.grid-cell__editor input,.grid-cell__editor moz-select{width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain{border:none;outline:none;background:transparent;font-family:inherit;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);padding:0;height:100%;width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain:focus{outline:none}.grid-cell__input--plain[type=number]::-webkit-inner-spin-button,.grid-cell__input--plain[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.grid-cell__input--plain[type=number]{-moz-appearance:textfield}.grid-cell__editor ::ng-deep .text-input{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .select,.grid-cell__editor ::ng-deep .select__trigger{min-width:0;width:100%}.grid-cell__editor ::ng-deep *{max-width:100%}.grid-cell__editor moz-datepicker{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .mc-datepicker,.grid-cell__editor ::ng-deep .mc-text-input{width:100%;min-width:0;box-sizing:border-box;height:28px;font-size:var(--font-size-xs, 12px)}.grid-cell__editor ::ng-deep moz-datepicker label{display:none}.grid-cell>*:not(.grid-cell__fill-handle){position:relative;z-index:1}.grid-cell:before{content:\"\";position:absolute;inset:3px;border-radius:4px;pointer-events:none;z-index:0}.grid-cell:hover:before{background:#f1f3f4}.grid-cell--focused:hover:before,.grid-cell--in-range:hover:before,.grid-cell--in-fill-range:hover:before,.grid-cell--in-fill-reject-range:hover:before{background:transparent}.grid-cell--focused{z-index:2}.grid-cell--focused:after{content:\"\";position:absolute;inset:0;border:2px solid var(--color-background-accent-inverse);border-radius:4px;pointer-events:none}.grid-cell--in-range{background:var(--color-background-accent)}.grid-cell--readonly .grid-cell__value{color:var(--color-text-secondary, #666)}.grid-cell--in-fill-range{background:#34a85314}.grid-cell--in-fill-range:after{content:\"\";position:absolute;inset:0;border:1px dashed var(--color-success, #34a853);border-radius:4px;pointer-events:none}.grid-cell--in-fill-reject-range{background:#ea302d1f;cursor:not-allowed;z-index:2}.grid-cell--in-fill-reject-range:after{content:\"\";position:absolute;inset:0;border:2px dashed var(--Status-Standalone-element-Error, #ea302d);border-radius:4px;pointer-events:none;z-index:3}.grid-cell--cut{background:#1a73e80f}.grid-cell__cut-mark{position:absolute;pointer-events:none;z-index:5;--cut-color: var(--color-background-accent-inverse, #1a73e8)}.grid-cell__cut-mark--top,.grid-cell__cut-mark--bottom{left:0;right:0;height:2px;background-image:linear-gradient(90deg,var(--cut-color) 50%,transparent 50%);background-size:8px 2px;background-repeat:repeat-x;animation:moz-grid-marching-ants-x .5s linear infinite}.grid-cell__cut-mark--top{top:0}.grid-cell__cut-mark--bottom{bottom:0}.grid-cell__cut-mark--left,.grid-cell__cut-mark--right{top:0;bottom:0;width:2px;background-image:linear-gradient(180deg,var(--cut-color) 50%,transparent 50%);background-size:2px 8px;background-repeat:repeat-y;animation:moz-grid-marching-ants-y .5s linear infinite}.grid-cell__cut-mark--left{left:0}.grid-cell__cut-mark--right{right:0}@keyframes moz-grid-marching-ants-x{0%{background-position-x:0}to{background-position-x:8px}}@keyframes moz-grid-marching-ants-y{0%{background-position-y:0}to{background-position-y:8px}}.grid-cell__fill-handle{position:absolute;right:0;bottom:0;width:8px;height:8px;background:var(--color-background-accent-inverse);cursor:crosshair;z-index:4}.grid-cell--error{outline:1px solid var(--Status-Border-Error, #ef5f5c);outline-offset:-2px;z-index:1;border-radius:4px}.grid-cell--error:hover:before{background:transparent}.grid-cell__error-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;cursor:help;position:relative;z-index:2;fill:var(--Status-Standalone-element-Error, #ea302d)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MozSelectComponent, selector: "moz-select", inputs: ["id", "name", "options", "placeholder", "isInvalid", "disabled", "readonly", "size"] }, { kind: "component", type: MozCheckboxComponent, selector: "moz-checkbox", inputs: ["id", "name", "label", "indeterminate", "isInvalid", "disabled", "indented"] }, { kind: "component", type: MozDatepickerComponent, selector: "moz-datepicker", inputs: ["id", "disabled", "readonly", "invalid", "error", "clearable", "size", "label"] }, { kind: "directive", type: MozTooltipDirective, selector: "[mozTooltip]", inputs: ["mozTooltip", "tooltipPosition", "tooltipNoPointer"] }, { kind: "component", type: ErrorFilled24, selector: "ErrorFilled24", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8954
9710
  }
8955
9711
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridCellComponent, decorators: [{
8956
9712
  type: Component,
@@ -8966,7 +9722,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8966
9722
  '[style.flex]': 'isLast() ? "1 0 auto" : "0 0 auto"',
8967
9723
  '[style.width.px]': 'isLast() ? undefined : colState().currentWidth',
8968
9724
  '[style.min-width.px]': 'isLast() ? colState().currentWidth : resolvedMinWidth()',
8969
- }, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--cut]=\"cutEdges().any\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (cutEdges(); as edges) { @if (edges.top) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--top\"></div>\n } @if (edges.bottom) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--bottom\"></div>\n } @if (edges.left) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--left\"></div>\n } @if (edges.right) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--right\"></div>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;min-width:0}.grid-cell{display:flex;align-items:center;position:relative;padding:0 var(--spacing-s, 8px);height:100%;border-right:1px solid var(--color-border-primary);overflow:hidden;box-sizing:border-box;min-width:0;background:inherit;cursor:pointer}.grid-cell--last{border-right:none}.grid-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-cell--pinned-end{border-left:none}.grid-cell__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);position:relative;z-index:1}.grid-cell__editor{width:100%;min-width:0;max-width:100%;height:100%;display:flex;align-items:center;overflow:hidden;box-sizing:border-box;position:relative;z-index:1}.grid-cell__editor-custom{width:100%;min-width:0;max-width:100%;overflow:hidden;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;gap:4px}.grid-cell__editor input,.grid-cell__editor moz-select{width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain{border:none;outline:none;background:transparent;font-family:inherit;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);padding:0;height:100%;width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain:focus{outline:none}.grid-cell__input--plain[type=number]::-webkit-inner-spin-button,.grid-cell__input--plain[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.grid-cell__input--plain[type=number]{-moz-appearance:textfield}.grid-cell__editor ::ng-deep .text-input{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .select,.grid-cell__editor ::ng-deep .select__trigger{min-width:0;width:100%}.grid-cell__editor ::ng-deep *{max-width:100%}.grid-cell__editor moz-datepicker{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .mc-datepicker,.grid-cell__editor ::ng-deep .mc-text-input{width:100%;min-width:0;box-sizing:border-box;height:28px;font-size:var(--font-size-xs, 12px)}.grid-cell__editor ::ng-deep moz-datepicker label{display:none}.grid-cell>*:not(.grid-cell__fill-handle){position:relative;z-index:1}.grid-cell:before{content:\"\";position:absolute;inset:3px;border-radius:4px;pointer-events:none;z-index:0}.grid-cell:hover:before{background:#f1f3f4}.grid-cell--focused:hover:before,.grid-cell--in-range:hover:before,.grid-cell--in-fill-range:hover:before,.grid-cell--in-fill-reject-range:hover:before{background:transparent}.grid-cell--focused{z-index:2}.grid-cell--focused:after{content:\"\";position:absolute;inset:0;border:2px solid var(--color-background-accent-inverse);border-radius:4px;pointer-events:none}.grid-cell--in-range{background:var(--color-background-accent)}.grid-cell--readonly .grid-cell__value{color:var(--color-text-secondary, #666)}.grid-cell--in-fill-range{background:#34a85314}.grid-cell--in-fill-range:after{content:\"\";position:absolute;inset:0;border:1px dashed var(--color-success, #34a853);border-radius:4px;pointer-events:none}.grid-cell--in-fill-reject-range{background:#ea302d1f;cursor:not-allowed;z-index:2}.grid-cell--in-fill-reject-range:after{content:\"\";position:absolute;inset:0;border:2px dashed var(--Status-Standalone-element-Error, #ea302d);border-radius:4px;pointer-events:none;z-index:3}.grid-cell--cut{background:#1a73e80f}.grid-cell__cut-mark{position:absolute;pointer-events:none;z-index:5;--cut-color: var(--color-background-accent-inverse, #1a73e8)}.grid-cell__cut-mark--top,.grid-cell__cut-mark--bottom{left:0;right:0;height:2px;background-image:linear-gradient(90deg,var(--cut-color) 50%,transparent 50%);background-size:8px 2px;background-repeat:repeat-x;animation:moz-grid-marching-ants-x .5s linear infinite}.grid-cell__cut-mark--top{top:0}.grid-cell__cut-mark--bottom{bottom:0}.grid-cell__cut-mark--left,.grid-cell__cut-mark--right{top:0;bottom:0;width:2px;background-image:linear-gradient(180deg,var(--cut-color) 50%,transparent 50%);background-size:2px 8px;background-repeat:repeat-y;animation:moz-grid-marching-ants-y .5s linear infinite}.grid-cell__cut-mark--left{left:0}.grid-cell__cut-mark--right{right:0}@keyframes moz-grid-marching-ants-x{0%{background-position-x:0}to{background-position-x:8px}}@keyframes moz-grid-marching-ants-y{0%{background-position-y:0}to{background-position-y:8px}}.grid-cell__fill-handle{position:absolute;right:0;bottom:0;width:8px;height:8px;background:var(--color-background-accent-inverse);cursor:crosshair;z-index:4}.grid-cell--error{background:var(--Background-Primary, #fff);outline:1px solid var(--Status-Border-Error, #ef5f5c);outline-offset:-2px;z-index:1;border-radius:4px}.grid-cell--error:hover:before{background:transparent}.grid-cell__error-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;cursor:help;position:relative;z-index:2}.grid-cell__error-icon ::ng-deep svg{fill:var(--Status-Standalone-element-Error, #ea302d)}\n"] }]
9725
+ }, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--cut]=\"cutEdges().any\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (cutEdges(); as edges) { @if (edges.top) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--top\"></div>\n } @if (edges.bottom) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--bottom\"></div>\n } @if (edges.left) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--left\"></div>\n } @if (edges.right) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--right\"></div>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;min-width:0}.grid-cell{display:flex;align-items:center;position:relative;padding:0 var(--spacing-s, 8px);height:100%;border-right:1px solid var(--color-border-primary);overflow:hidden;box-sizing:border-box;min-width:0;background:inherit;cursor:pointer}.grid-cell--last{border-right:none}.grid-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-cell--pinned-end{border-left:none}.grid-cell__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);position:relative;z-index:1}.grid-cell__editor{width:100%;min-width:0;max-width:100%;height:100%;display:flex;align-items:center;overflow:hidden;box-sizing:border-box;position:relative;z-index:1}.grid-cell__editor-custom{width:100%;min-width:0;max-width:100%;overflow:hidden;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;gap:4px}.grid-cell__editor input,.grid-cell__editor moz-select{width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain{border:none;outline:none;background:transparent;font-family:inherit;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);padding:0;height:100%;width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain:focus{outline:none}.grid-cell__input--plain[type=number]::-webkit-inner-spin-button,.grid-cell__input--plain[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.grid-cell__input--plain[type=number]{-moz-appearance:textfield}.grid-cell__editor ::ng-deep .text-input{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .select,.grid-cell__editor ::ng-deep .select__trigger{min-width:0;width:100%}.grid-cell__editor ::ng-deep *{max-width:100%}.grid-cell__editor moz-datepicker{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .mc-datepicker,.grid-cell__editor ::ng-deep .mc-text-input{width:100%;min-width:0;box-sizing:border-box;height:28px;font-size:var(--font-size-xs, 12px)}.grid-cell__editor ::ng-deep moz-datepicker label{display:none}.grid-cell>*:not(.grid-cell__fill-handle){position:relative;z-index:1}.grid-cell:before{content:\"\";position:absolute;inset:3px;border-radius:4px;pointer-events:none;z-index:0}.grid-cell:hover:before{background:#f1f3f4}.grid-cell--focused:hover:before,.grid-cell--in-range:hover:before,.grid-cell--in-fill-range:hover:before,.grid-cell--in-fill-reject-range:hover:before{background:transparent}.grid-cell--focused{z-index:2}.grid-cell--focused:after{content:\"\";position:absolute;inset:0;border:2px solid var(--color-background-accent-inverse);border-radius:4px;pointer-events:none}.grid-cell--in-range{background:var(--color-background-accent)}.grid-cell--readonly .grid-cell__value{color:var(--color-text-secondary, #666)}.grid-cell--in-fill-range{background:#34a85314}.grid-cell--in-fill-range:after{content:\"\";position:absolute;inset:0;border:1px dashed var(--color-success, #34a853);border-radius:4px;pointer-events:none}.grid-cell--in-fill-reject-range{background:#ea302d1f;cursor:not-allowed;z-index:2}.grid-cell--in-fill-reject-range:after{content:\"\";position:absolute;inset:0;border:2px dashed var(--Status-Standalone-element-Error, #ea302d);border-radius:4px;pointer-events:none;z-index:3}.grid-cell--cut{background:#1a73e80f}.grid-cell__cut-mark{position:absolute;pointer-events:none;z-index:5;--cut-color: var(--color-background-accent-inverse, #1a73e8)}.grid-cell__cut-mark--top,.grid-cell__cut-mark--bottom{left:0;right:0;height:2px;background-image:linear-gradient(90deg,var(--cut-color) 50%,transparent 50%);background-size:8px 2px;background-repeat:repeat-x;animation:moz-grid-marching-ants-x .5s linear infinite}.grid-cell__cut-mark--top{top:0}.grid-cell__cut-mark--bottom{bottom:0}.grid-cell__cut-mark--left,.grid-cell__cut-mark--right{top:0;bottom:0;width:2px;background-image:linear-gradient(180deg,var(--cut-color) 50%,transparent 50%);background-size:2px 8px;background-repeat:repeat-y;animation:moz-grid-marching-ants-y .5s linear infinite}.grid-cell__cut-mark--left{left:0}.grid-cell__cut-mark--right{right:0}@keyframes moz-grid-marching-ants-x{0%{background-position-x:0}to{background-position-x:8px}}@keyframes moz-grid-marching-ants-y{0%{background-position-y:0}to{background-position-y:8px}}.grid-cell__fill-handle{position:absolute;right:0;bottom:0;width:8px;height:8px;background:var(--color-background-accent-inverse);cursor:crosshair;z-index:4}.grid-cell--error{outline:1px solid var(--Status-Border-Error, #ef5f5c);outline-offset:-2px;z-index:1;border-radius:4px}.grid-cell--error:hover:before{background:transparent}.grid-cell__error-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;cursor:help;position:relative;z-index:2;fill:var(--Status-Standalone-element-Error, #ea302d)}\n"] }]
8970
9726
  }], ctorParameters: () => [], propDecorators: { row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: true }] }], rowIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIndex", required: true }] }], colIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "colIndex", required: true }] }], colState: [{ type: i0.Input, args: [{ isSignal: true, alias: "colState", required: true }] }], def: [{ type: i0.Input, args: [{ isSignal: true, alias: "def", required: true }] }], isLast: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLast", required: false }] }], pinnedEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedEnd", required: false }] }], commitEdit: [{ type: i0.Output, args: ["commitEdit"] }], cancelEdit: [{ type: i0.Output, args: ["cancelEdit"] }] } });
8971
9727
 
8972
9728
  class MozGridRowComponent {
@@ -9541,6 +10297,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
9541
10297
  args: [{ selector: 'moz-grid-keyboard-shortcuts-drawer', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MozButtonComponent, MozDrawerFooterDirective], template: "<div class=\"shortcuts\">\n @for (group of groups; track group.title) {\n <section class=\"shortcuts__group\">\n <h4 class=\"shortcuts__group-title\">{{ group.title }}</h4>\n <dl class=\"shortcuts__list\">\n @for (item of group.items; track item.keys) {\n <div class=\"shortcuts__item\">\n <dt class=\"shortcuts__keys\">\n @for (part of item.parts; track $index) { @if (part === '+' || part === '/') {\n <span class=\"shortcuts__separator\">{{ part }}</span>\n } @else {\n <kbd class=\"shortcuts__key\">{{ part }}</kbd>\n } }\n </dt>\n <dd class=\"shortcuts__label\">{{ item.label }}</dd>\n </div>\n }\n </dl>\n </section>\n }\n</div>\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"close()\">Close</button>\n</ng-template>\n", styles: [".shortcuts{padding-bottom:24px}.shortcuts__group{margin-bottom:24px}.shortcuts__group:last-child{margin-bottom:0}.shortcuts__group-title{margin:0 0 8px;font-size:var(--font-size-s, 14px);font-weight:700;color:var(--color-text-secondary);text-transform:uppercase;letter-spacing:.5px}.shortcuts__list{display:flex;flex-direction:column;margin:0;padding:0}.shortcuts__item{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--color-border-primary)}.shortcuts__item:last-child{border-bottom:none}.shortcuts__label{margin:0;flex:1;font-size:var(--font-size-s, 14px);color:var(--color-text-primary)}.shortcuts__keys{display:flex;align-items:center;flex-wrap:wrap;gap:4px;flex-shrink:0;margin:0}.shortcuts__key{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;padding:0 6px;background:var(--color-background-secondary, #f5f5f5);border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);font-family:var(--font-family-monospace, \"SFMono-Regular\", Consolas, monospace);font-size:var(--font-size-50, 12px);font-weight:600;color:var(--color-text-primary);box-shadow:inset 0 -1px #00000014}.shortcuts__separator{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);padding:0 2px}\n"] }]
9542
10298
  }] });
9543
10299
 
10300
+ class MozGridFilterDrawerComponent {
10301
+ drawerRef = inject(MozDrawerRef);
10302
+ data = inject(DRAWER_DATA);
10303
+ availableColumns = this.data.availableColumns;
10304
+ draft = signal({
10305
+ conditions: this.data.model.conditions.map((c) => ({ ...c, value: { ...c.value } })),
10306
+ }, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
10307
+ activeCount = computed(() => this.draft().conditions.length, ...(ngDevMode ? [{ debugName: "activeCount" }] : /* istanbul ignore next */ []));
10308
+ onDraftChange(model) {
10309
+ this.draft.set(model);
10310
+ }
10311
+ apply() {
10312
+ this.drawerRef.close({ model: this.draft(), applied: true });
10313
+ }
10314
+ clearAll() {
10315
+ this.draft.set({ conditions: [] });
10316
+ }
10317
+ cancel() {
10318
+ this.drawerRef.close({ model: this.data.model, applied: false });
10319
+ }
10320
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10321
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: MozGridFilterDrawerComponent, isStandalone: true, selector: "moz-grid-filter-drawer", ngImport: i0, template: "<div class=\"filter-drawer\">\n <p class=\"filter-drawer__subtitle\">{{ activeCount() }} active filter(s)</p>\n <moz-grid-filter-builder\n [model]=\"draft()\"\n [availableColumns]=\"availableColumns\"\n [applyMode]=\"'manual'\"\n [showSubtitle]=\"false\"\n (modelChange)=\"onDraftChange($event)\"\n />\n</div>\n\n<ng-template mozDrawerFooter>\n <button moz-button [appearance]=\"'accent'\" (click)=\"apply()\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"clearAll()\">Clear all</button>\n</ng-template>\n", styles: [".filter-drawer{padding-bottom:16px}.filter-drawer__subtitle{margin:0 16px 8px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--Text-Secondary, #555)}\n"], dependencies: [{ kind: "component", type: MozGridFilterBuilderComponent, selector: "moz-grid-filter-builder", inputs: ["model", "availableColumns", "applyMode", "showSubtitle"], outputs: ["modelChange"] }, { kind: "component", type: MozButtonComponent, selector: "button[moz-button]", inputs: ["appearance", "size", "disabled", "ghost", "outlined", "iconPosition", "type", "isLoading"] }, { kind: "directive", type: MozDrawerFooterDirective, selector: "[mozDrawerFooter]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10322
+ }
10323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterDrawerComponent, decorators: [{
10324
+ type: Component,
10325
+ args: [{ selector: 'moz-grid-filter-drawer', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MozGridFilterBuilderComponent, MozButtonComponent, MozDrawerFooterDirective], template: "<div class=\"filter-drawer\">\n <p class=\"filter-drawer__subtitle\">{{ activeCount() }} active filter(s)</p>\n <moz-grid-filter-builder\n [model]=\"draft()\"\n [availableColumns]=\"availableColumns\"\n [applyMode]=\"'manual'\"\n [showSubtitle]=\"false\"\n (modelChange)=\"onDraftChange($event)\"\n />\n</div>\n\n<ng-template mozDrawerFooter>\n <button moz-button [appearance]=\"'accent'\" (click)=\"apply()\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"clearAll()\">Clear all</button>\n</ng-template>\n", styles: [".filter-drawer{padding-bottom:16px}.filter-drawer__subtitle{margin:0 16px 8px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--Text-Secondary, #555)}\n"] }]
10326
+ }] });
10327
+
9544
10328
  class MozGridComponent {
9545
10329
  state = inject(GridStateManager);
9546
10330
  gridEngine = inject(GridEngine);
@@ -9568,6 +10352,7 @@ class MozGridComponent {
9568
10352
  // --- Content children ---
9569
10353
  columnDefs = contentChildren(MozGridColumnDef, ...(ngDevMode ? [{ debugName: "columnDefs" }] : /* istanbul ignore next */ []));
9570
10354
  toolbarDefs = contentChildren(MozGridToolbarDef, ...(ngDevMode ? [{ debugName: "toolbarDefs" }] : /* istanbul ignore next */ []));
10355
+ emptyDefs = contentChildren(MozGridEmptyDef, ...(ngDevMode ? [{ debugName: "emptyDefs" }] : /* istanbul ignore next */ []));
9571
10356
  // --- Inputs ---
9572
10357
  data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
9573
10358
  mode = input('client', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
@@ -9584,6 +10369,17 @@ class MozGridComponent {
9584
10369
  fullscreen = input(false, ...(ngDevMode ? [{ debugName: "fullscreen" }] : /* istanbul ignore next */ []));
9585
10370
  reorderable = input(false, ...(ngDevMode ? [{ debugName: "reorderable" }] : /* istanbul ignore next */ []));
9586
10371
  stateKey = input(null, ...(ngDevMode ? [{ debugName: "stateKey" }] : /* istanbul ignore next */ []));
10372
+ // --- Empty state customization ---
10373
+ /** Title for the default "no data" empty state. */
10374
+ emptyDataTitle = input('', ...(ngDevMode ? [{ debugName: "emptyDataTitle" }] : /* istanbul ignore next */ []));
10375
+ /** Description for the default "no data" empty state. */
10376
+ emptyDataDescription = input('', ...(ngDevMode ? [{ debugName: "emptyDataDescription" }] : /* istanbul ignore next */ []));
10377
+ /** Title for the default "no results" empty state (active filters). */
10378
+ noResultsTitle = input('', ...(ngDevMode ? [{ debugName: "noResultsTitle" }] : /* istanbul ignore next */ []));
10379
+ /** Description for the default "no results" empty state. */
10380
+ noResultsDescription = input('', ...(ngDevMode ? [{ debugName: "noResultsDescription" }] : /* istanbul ignore next */ []));
10381
+ /** CTA label on the "no results" state. Empty disables the button. */
10382
+ noResultsActionLabel = input('Clear filters', ...(ngDevMode ? [{ debugName: "noResultsActionLabel" }] : /* istanbul ignore next */ []));
9587
10383
  exportable = input(false, ...(ngDevMode ? [{ debugName: "exportable" }] : /* istanbul ignore next */ []));
9588
10384
  horizontalVirtualScroll = input(false, ...(ngDevMode ? [{ debugName: "horizontalVirtualScroll" }] : /* istanbul ignore next */ []));
9589
10385
  loadingStrategy = input('pagination', ...(ngDevMode ? [{ debugName: "loadingStrategy" }] : /* istanbul ignore next */ []));
@@ -9608,7 +10404,88 @@ class MozGridComponent {
9608
10404
  // --- Internal ---
9609
10405
  isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : /* istanbul ignore next */ []));
9610
10406
  groupPanelOpen = signal(false, ...(ngDevMode ? [{ debugName: "groupPanelOpen" }] : /* istanbul ignore next */ []));
9611
- filterPanelOpen = signal(false, ...(ngDevMode ? [{ debugName: "filterPanelOpen" }] : /* istanbul ignore next */ []));
10407
+ /**
10408
+ * Mode controlling how the builder emits `filterChange`:
10409
+ * - `auto` : each mutation triggers an event (default in `client` mode).
10410
+ * - `manual` : only an explicit Apply emits (default in `server` mode).
10411
+ */
10412
+ filterApplyMode = input(null, ...(ngDevMode ? [{ debugName: "filterApplyMode" }] : /* istanbul ignore next */ []));
10413
+ resolvedFilterApplyMode = computed(() => {
10414
+ const override = this.filterApplyMode();
10415
+ if (override)
10416
+ return override;
10417
+ return this.state.mode() === 'server' ? 'manual' : 'auto';
10418
+ }, ...(ngDevMode ? [{ debugName: "resolvedFilterApplyMode" }] : /* istanbul ignore next */ []));
10419
+ /** Display descriptors for the "FILTERED BY" tag bar. */
10420
+ activeFilters = computed(() => {
10421
+ return this.filterEngine.conditions().map((c) => ({
10422
+ id: c.id,
10423
+ field: c.field,
10424
+ label: this.filterEngine.toLabel(c),
10425
+ removable: true,
10426
+ }));
10427
+ }, ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
10428
+ activeFilterCount = computed(() => this.filterEngine.conditions().length, ...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
10429
+ /**
10430
+ * Kind of empty state to show, or `'none'` when rows are present:
10431
+ * - `'no-data'` : no rows have been loaded (and the source is empty).
10432
+ * - `'no-results'` : the source has rows but the active filters yield 0.
10433
+ *
10434
+ * Loading and infinite-scroll loading-more states are *not* treated as
10435
+ * empty (we let the loading indicator drive the UX instead).
10436
+ */
10437
+ emptyStateKind = computed(() => {
10438
+ if (this.gridEngine.displayRows().length > 0)
10439
+ return 'none';
10440
+ if (this.state.isLoading())
10441
+ return 'none';
10442
+ const hasFilters = this.filterEngine.conditions().length > 0;
10443
+ const sourceCount = this.state.mode() === 'server' ? this.state.totalItems() : this.state.sourceData().length;
10444
+ // In server mode `totalItems` may not reflect filters, so trust active
10445
+ // filters as the discriminator. Same heuristic in client mode.
10446
+ if (hasFilters)
10447
+ return 'no-results';
10448
+ if (sourceCount === 0)
10449
+ return 'no-data';
10450
+ // Source has rows, no filters, but display is empty (e.g. group with no
10451
+ // matching rows after pagination). Fall back to `no-results` which
10452
+ // offers the "Clear filters" CTA — harmless when no filters are active
10453
+ // because we hide the button when the count is zero.
10454
+ return 'no-results';
10455
+ }, ...(ngDevMode ? [{ debugName: "emptyStateKind" }] : /* istanbul ignore next */ []));
10456
+ /** Resolves the projected `<ng-template mozGridEmptyDef>` for the kind. */
10457
+ emptyTemplate = computed(() => {
10458
+ const kind = this.emptyStateKind();
10459
+ if (kind === 'none')
10460
+ return null;
10461
+ const defs = this.emptyDefs();
10462
+ const exact = defs.find((d) => d.kind() === kind);
10463
+ if (exact)
10464
+ return exact.template;
10465
+ // Fallback to a `no-data` template for the `no-results` kind.
10466
+ return defs.find((d) => d.kind() === 'no-data')?.template ?? null;
10467
+ }, ...(ngDevMode ? [{ debugName: "emptyTemplate" }] : /* istanbul ignore next */ []));
10468
+ /** Context object exposed to projected empty-state templates. */
10469
+ emptyContext = computed(() => ({
10470
+ activeFilterCount: this.filterEngine.conditions().length,
10471
+ clearFilters: () => this.filterEngine.clearAll(),
10472
+ }), ...(ngDevMode ? [{ debugName: "emptyContext" }] : /* istanbul ignore next */ []));
10473
+ /**
10474
+ * Wrap the empty context for `ngTemplateOutlet` so consumers can use
10475
+ * either `let-ctx` (positional, via `$implicit`) or named bindings
10476
+ * (`let-clearFilters="clearFilters"`) without having to choose at
10477
+ * declaration time.
10478
+ */
10479
+ emptyTemplateContext = computed(() => {
10480
+ const ctx = this.emptyContext();
10481
+ return { $implicit: ctx, ...ctx };
10482
+ }, ...(ngDevMode ? [{ debugName: "emptyTemplateContext" }] : /* istanbul ignore next */ []));
10483
+ /** CTA label for the default no-results state — hidden when no filters. */
10484
+ resolvedNoResultsActionLabel = computed(() => {
10485
+ if (this.filterEngine.conditions().length === 0)
10486
+ return '';
10487
+ return this.noResultsActionLabel();
10488
+ }, ...(ngDevMode ? [{ debugName: "resolvedNoResultsActionLabel" }] : /* istanbul ignore next */ []));
9612
10489
  hasHiddenColumns = computed(() => this.state.columnStates().some((col) => !col.visible), ...(ngDevMode ? [{ debugName: "hasHiddenColumns" }] : /* istanbul ignore next */ []));
9613
10490
  hiddenColumnsList = computed(() => {
9614
10491
  const defMap = this.state.columnDefMap();
@@ -9799,7 +10676,7 @@ class MozGridComponent {
9799
10676
  this.persistenceEngine.restore(key);
9800
10677
  }
9801
10678
  }, { allowSignalWrites: true });
9802
- // Auto-save state on column/sort changes
10679
+ // Auto-save state on column/sort/filter changes
9803
10680
  effect(() => {
9804
10681
  const key = this.stateKey();
9805
10682
  if (!key)
@@ -9807,8 +10684,19 @@ class MozGridComponent {
9807
10684
  // Read signals to track them
9808
10685
  this.state.columnStates();
9809
10686
  this.state.activeSorts();
10687
+ this.state.filterModel();
9810
10688
  this.persistenceEngine.save(key);
9811
10689
  });
10690
+ // Emit `filterChange` once per mutation. The engine's `lastEvent` signal
10691
+ // is set synchronously from every mutation path, guaranteeing we emit
10692
+ // exactly once per reason (add / update / remove / reorder / clear / replace).
10693
+ effect(() => {
10694
+ const event = this.filterEngine.lastEvent();
10695
+ if (!event)
10696
+ return;
10697
+ this.filterChange.emit(event);
10698
+ this.resetInfiniteScrollIfActive();
10699
+ });
9812
10700
  // Initialize plugins
9813
10701
  effect(() => {
9814
10702
  const pluginList = this.plugins();
@@ -10306,17 +11194,27 @@ class MozGridComponent {
10306
11194
  this.state.activeSelectionMode.set('none');
10307
11195
  this.selectionChange.emit(this.rowSelectionEngine.getSelectionEvent());
10308
11196
  }
10309
- onRemoveFilter(field) {
10310
- this.filterEngine.removeFilter(field);
10311
- this.filterChange.emit({ filters: this.state.activeFilters() });
10312
- this.resetInfiniteScrollIfActive();
11197
+ /** Removes a single condition by id (tag "×" button). */
11198
+ onRemoveFilter(id) {
11199
+ this.filterEngine.removeCondition(id);
10313
11200
  }
11201
+ /** Clears the whole filter model ("Remove all" button). */
10314
11202
  onRemoveAllFilters() {
10315
- for (const filter of this.state.activeFilters()) {
10316
- this.filterEngine.removeFilter(filter.field);
10317
- }
10318
- this.filterChange.emit({ filters: this.state.activeFilters() });
10319
- this.resetInfiniteScrollIfActive();
11203
+ this.filterEngine.clearAll();
11204
+ }
11205
+ // --- Filter drawer ---
11206
+ onFiltersClick() {
11207
+ const data = {
11208
+ model: { conditions: this.filterEngine.conditions().slice() },
11209
+ availableColumns: this.filterEngine.describeFilterableColumns(),
11210
+ applyMode: this.resolvedFilterApplyMode(),
11211
+ };
11212
+ const ref = this.drawerService.open(MozGridFilterDrawerComponent, { title: 'Filters', data, extended: true });
11213
+ ref.afterClosed().subscribe((result) => {
11214
+ if (result?.applied) {
11215
+ this.filterEngine.setModel(result.model, 'replace');
11216
+ }
11217
+ });
10320
11218
  }
10321
11219
  onRestoreColumn(field) {
10322
11220
  this.state.updateColumnState(field, { visible: true });
@@ -10535,21 +11433,37 @@ class MozGridComponent {
10535
11433
  return;
10536
11434
  try {
10537
11435
  const text = await navigator.clipboard.readText();
10538
- const rows = text.split('\n').map((line) => line.split('\t'));
11436
+ const rows = text
11437
+ .replace(/\r?\n$/, '')
11438
+ .split('\n')
11439
+ .map((line) => line.split('\t'));
10539
11440
  const cols = this.state.visibleColumns();
10540
- // Build a paste range starting from the focused cell
10541
- const pasteRange = {
10542
- start: { row: focused.row, col: focused.col },
10543
- end: {
10544
- row: Math.min(focused.row + rows.length - 1, this.state.sourceData().length - 1),
10545
- col: Math.min(focused.col + (rows[0]?.length ?? 1) - 1, cols.length - 1),
10546
- },
10547
- };
11441
+ // Excel-style: a single clipboard value pasted over a multi-cell selection
11442
+ // fills the entire selection rather than only the focused anchor.
11443
+ const isSingleValue = rows.length === 1 && rows[0].length === 1;
11444
+ const selection = this.cellSelectionEngine.getNormalizedRange();
11445
+ const isMultiCellSelection = !!selection &&
11446
+ (selection.start.row !== selection.end.row || selection.start.col !== selection.end.col);
11447
+ let pasteRange;
11448
+ let pasteChanges;
11449
+ if (isSingleValue && isMultiCellSelection && selection) {
11450
+ pasteRange = selection;
11451
+ pasteChanges = this.clipboardEngine.fillSelection(selection, rows[0][0]);
11452
+ }
11453
+ else {
11454
+ pasteRange = {
11455
+ start: { row: focused.row, col: focused.col },
11456
+ end: {
11457
+ row: Math.min(focused.row + rows.length - 1, this.state.sourceData().length - 1),
11458
+ col: Math.min(focused.col + (rows[0]?.length ?? 1) - 1, cols.length - 1),
11459
+ },
11460
+ };
11461
+ pasteChanges = this.clipboardEngine.applyPaste(pasteRange, rows);
11462
+ }
10548
11463
  // If a cut is pending, first wipe the source cells so cut+paste == move,
10549
11464
  // and fold both halves into a single undoable history op.
10550
11465
  const cutSource = this.state.cutSource();
10551
11466
  const clearChanges = cutSource ? this.clipboardEngine.clearRange(cutSource) : [];
10552
- const pasteChanges = this.clipboardEngine.applyPaste(pasteRange, rows);
10553
11467
  const allChanges = [...clearChanges, ...pasteChanges];
10554
11468
  if (allChanges.length > 0) {
10555
11469
  this.historyEngine.record(cutSource ? 'cut' : 'paste', allChanges);
@@ -10800,7 +11714,7 @@ class MozGridComponent {
10800
11714
  return value;
10801
11715
  }
10802
11716
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10803
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridComponent, isStandalone: true, selector: "moz-grid", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rowSelection: { classPropertyName: "rowSelection", publicName: "rowSelection", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, rowIdField: { classPropertyName: "rowIdField", publicName: "rowIdField", isSignal: true, isRequired: false, transformFunction: null }, detailTemplate: { classPropertyName: "detailTemplate", publicName: "detailTemplate", isSignal: true, isRequired: false, transformFunction: null }, fullscreen: { classPropertyName: "fullscreen", publicName: "fullscreen", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, stateKey: { classPropertyName: "stateKey", publicName: "stateKey", isSignal: true, isRequired: false, transformFunction: null }, exportable: { classPropertyName: "exportable", publicName: "exportable", isSignal: true, isRequired: false, transformFunction: null }, horizontalVirtualScroll: { classPropertyName: "horizontalVirtualScroll", publicName: "horizontalVirtualScroll", isSignal: true, isRequired: false, transformFunction: null }, loadingStrategy: { classPropertyName: "loadingStrategy", publicName: "loadingStrategy", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, plugins: { classPropertyName: "plugins", publicName: "plugins", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", pageChange: "pageChange", loadMore: "loadMore", cellEdit: "cellEdit", cellEditCancel: "cellEditCancel", selectionChange: "selectionChange", cellSelectionChange: "cellSelectionChange", groupChange: "groupChange", filterChange: "filterChange", bulkEdit: "bulkEdit", bulkCopy: "bulkCopy", bulkPaste: "bulkPaste", bulkDelete: "bulkDelete", fillDown: "fillDown", settingsChange: "settingsChange" }, providers: [
11717
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridComponent, isStandalone: true, selector: "moz-grid", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rowSelection: { classPropertyName: "rowSelection", publicName: "rowSelection", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, rowIdField: { classPropertyName: "rowIdField", publicName: "rowIdField", isSignal: true, isRequired: false, transformFunction: null }, detailTemplate: { classPropertyName: "detailTemplate", publicName: "detailTemplate", isSignal: true, isRequired: false, transformFunction: null }, fullscreen: { classPropertyName: "fullscreen", publicName: "fullscreen", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, stateKey: { classPropertyName: "stateKey", publicName: "stateKey", isSignal: true, isRequired: false, transformFunction: null }, emptyDataTitle: { classPropertyName: "emptyDataTitle", publicName: "emptyDataTitle", isSignal: true, isRequired: false, transformFunction: null }, emptyDataDescription: { classPropertyName: "emptyDataDescription", publicName: "emptyDataDescription", isSignal: true, isRequired: false, transformFunction: null }, noResultsTitle: { classPropertyName: "noResultsTitle", publicName: "noResultsTitle", isSignal: true, isRequired: false, transformFunction: null }, noResultsDescription: { classPropertyName: "noResultsDescription", publicName: "noResultsDescription", isSignal: true, isRequired: false, transformFunction: null }, noResultsActionLabel: { classPropertyName: "noResultsActionLabel", publicName: "noResultsActionLabel", isSignal: true, isRequired: false, transformFunction: null }, exportable: { classPropertyName: "exportable", publicName: "exportable", isSignal: true, isRequired: false, transformFunction: null }, horizontalVirtualScroll: { classPropertyName: "horizontalVirtualScroll", publicName: "horizontalVirtualScroll", isSignal: true, isRequired: false, transformFunction: null }, loadingStrategy: { classPropertyName: "loadingStrategy", publicName: "loadingStrategy", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, plugins: { classPropertyName: "plugins", publicName: "plugins", isSignal: true, isRequired: false, transformFunction: null }, filterApplyMode: { classPropertyName: "filterApplyMode", publicName: "filterApplyMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", pageChange: "pageChange", loadMore: "loadMore", cellEdit: "cellEdit", cellEditCancel: "cellEditCancel", selectionChange: "selectionChange", cellSelectionChange: "cellSelectionChange", groupChange: "groupChange", filterChange: "filterChange", bulkEdit: "bulkEdit", bulkCopy: "bulkCopy", bulkPaste: "bulkPaste", bulkDelete: "bulkDelete", fillDown: "fillDown", settingsChange: "settingsChange" }, providers: [
10804
11718
  GridStateManager,
10805
11719
  GridEngine,
10806
11720
  SortEngine,
@@ -10824,7 +11738,7 @@ class MozGridComponent {
10824
11738
  PaginationEngine,
10825
11739
  InfiniteScrollEngine,
10826
11740
  Overlay,
10827
- ], queries: [{ propertyName: "columnDefs", predicate: MozGridColumnDef, isSignal: true }, { propertyName: "toolbarDefs", predicate: MozGridToolbarDef, isSignal: true }], viewQueries: [{ propertyName: "gridBody", first: true, predicate: MozGridBodyComponent, descendants: true, isSignal: true }, { propertyName: "gridContainer", first: true, predicate: ["gridContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
11741
+ ], queries: [{ propertyName: "columnDefs", predicate: MozGridColumnDef, isSignal: true }, { propertyName: "toolbarDefs", predicate: MozGridToolbarDef, isSignal: true }, { propertyName: "emptyDefs", predicate: MozGridEmptyDef, isSignal: true }], viewQueries: [{ propertyName: "gridBody", first: true, predicate: MozGridBodyComponent, descendants: true, isSignal: true }, { propertyName: "gridContainer", first: true, predicate: ["gridContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
10828
11742
  <div class="moz-grid-wrapper" [class.moz-grid-wrapper--fullscreen]="isFullscreen()">
10829
11743
  <!-- Toolbar (outside .moz-grid) -->
10830
11744
  <div class="moz-grid__toolbar">
@@ -10854,15 +11768,24 @@ class MozGridComponent {
10854
11768
  <Download20 icon />
10855
11769
  </moz-icon-button>
10856
11770
  }
10857
- <moz-icon-button
11771
+ <button
11772
+ type="button"
11773
+ moz-button
10858
11774
  id="grid-filter"
10859
11775
  size="s"
10860
11776
  [ghost]="true"
10861
- ariaLabel="Filter"
10862
- (activated)="filterPanelOpen.set(!filterPanelOpen())"
11777
+ [ariaLabel]="'Filters'"
11778
+ class="moz-grid__toolbar-filter-btn"
11779
+ (click)="onFiltersClick()"
10863
11780
  >
10864
11781
  <Filter20 icon />
10865
- </moz-icon-button>
11782
+ <span>Filters</span>
11783
+ @if (activeFilterCount() > 0) {
11784
+ <span class="moz-grid__toolbar-filter-badge" aria-hidden="true">
11785
+ {{ activeFilterCount() }}
11786
+ </span>
11787
+ }
11788
+ </button>
10866
11789
  <moz-icon-button
10867
11790
  id="grid-settings"
10868
11791
  size="s"
@@ -10972,15 +11895,15 @@ class MozGridComponent {
10972
11895
  </button>
10973
11896
  }
10974
11897
  </div>
10975
- } @if (state.activeFilters().length > 0) {
11898
+ } @if (activeFilters().length > 0) {
10976
11899
  <div class="moz-grid__tag-bar">
10977
11900
  <span class="moz-grid__tag-bar-label">FILTERED BY</span>
10978
- @for (filter of state.activeFilters(); track filter.field) {
11901
+ @for (filter of activeFilters(); track filter.id) {
10979
11902
  <moz-tag
10980
11903
  [type]="filter.removable ? 'removable' : 'informative'"
10981
11904
  size="s"
10982
- [id]="'filter-' + filter.field"
10983
- (removeTag)="onRemoveFilter(filter.field)"
11905
+ [id]="'filter-' + filter.id"
11906
+ (removeTag)="onRemoveFilter(filter.id)"
10984
11907
  >{{ filter.label }}</moz-tag
10985
11908
  >
10986
11909
  }
@@ -11022,6 +11945,25 @@ class MozGridComponent {
11022
11945
  (rowSelectionToggle)="onRowSelectionToggle()"
11023
11946
  />
11024
11947
 
11948
+ <!-- Empty state overlay (consumer template wins, fallback default) -->
11949
+ @if (emptyStateKind() !== 'none') {
11950
+ <div class="moz-grid__empty-overlay">
11951
+ @if (emptyTemplate(); as tpl) {
11952
+ <ng-container *ngTemplateOutlet="tpl; context: emptyTemplateContext()" />
11953
+ } @else {
11954
+ <moz-grid-empty-state
11955
+ [kind]="$any(emptyStateKind())"
11956
+ [title]="emptyStateKind() === 'no-results' ? noResultsTitle() : emptyDataTitle()"
11957
+ [description]="
11958
+ emptyStateKind() === 'no-results' ? noResultsDescription() : emptyDataDescription()
11959
+ "
11960
+ [actionLabel]="emptyStateKind() === 'no-results' ? resolvedNoResultsActionLabel() : ''"
11961
+ (action)="onRemoveAllFilters()"
11962
+ />
11963
+ }
11964
+ </div>
11965
+ }
11966
+
11025
11967
  <!-- Footer: pagination or infinite scroll loading indicator -->
11026
11968
  @if (showPagination()) {
11027
11969
  <moz-grid-footer
@@ -11042,7 +11984,7 @@ class MozGridComponent {
11042
11984
  />
11043
11985
  </div>
11044
11986
  </div>
11045
- `, isInline: true, styles: [":host{display:block;height:100%}.moz-grid-wrapper{display:flex;flex-direction:column;font-family:var(--font-family-primary);height:100%;min-height:0;gap:16px}.moz-grid-wrapper--fullscreen{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:var(--color-background-primary)}.moz-grid{display:flex;flex-direction:column;border-radius:var(--border-radius-l);overflow:hidden;background:var(--color-background-primary);flex:1;min-height:0;position:relative;box-shadow:0 0 6px #cdd4d8}.moz-grid:focus{outline:none}.moz-grid--loading{opacity:.6;pointer-events:none}.moz-grid__toolbar{display:flex;align-items:center;justify-content:space-between;flex-shrink:0;min-height:48px;gap:var(--spacing-s, 8px)}.moz-grid__toolbar-left,.moz-grid__toolbar-right{display:flex;align-items:center;gap:var(--spacing-xs, 4px)}.moz-grid__selection-banner{display:flex;align-items:center;gap:var(--spacing-s, 8px);flex:1;justify-content:center;border-radius:var(--border-radius-s);border:1px solid var(--Border-Primary, #cdd4d8);background:var(--Neutral-Grey-000, #fff)}.moz-grid__selection-text{font-size:var(--font-size-s, 14px);color:var(--color-text-primary);white-space:nowrap}.moz-grid__selection-link{padding:0;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-s, 14px);font-weight:600;cursor:pointer;white-space:nowrap;text-decoration:underline}.moz-grid__selection-link:hover{color:var(--color-primary-dark, #1557b0)}.moz-grid__tag-bar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--spacing-xs, 4px);padding:var(--spacing-xxs, 2px) var(--spacing-s, 8px);flex-shrink:0}.moz-grid__tag-bar-label{width:100%;font-size:var(--font-size-xs, 12px);text-transform:uppercase;white-space:nowrap;color:var(--text-icon-tertiary);font-size:var(--Typography-Font-size-Body-XS, 12px);font-weight:400}.moz-grid__tag-action-btn{padding:2px 8px;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-xs, 12px);font-weight:600;cursor:pointer}.moz-grid__tag-action-btn:hover{text-decoration:underline}.moz-grid__group-tag-btn{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;cursor:pointer;font:inherit;color:inherit;line-height:1}.moz-grid__group-tag-btn ::ng-deep svg{fill:#fff!important;width:16px;height:16px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MozGridHeaderComponent, selector: "moz-grid-header", inputs: ["showCheckbox", "showExpand", "reorderable"], outputs: ["sortClick", "menuAction", "resizeStart", "selectAllToggle", "columnReorder"] }, { kind: "component", type: MozGridBodyComponent, selector: "moz-grid-body", inputs: ["showCheckbox", "showExpand", "detailTemplate"], outputs: ["cellEdit", "cellEditCancel", "rowSelectionToggle", "groupToggle"] }, { kind: "component", type: MozGridFooterComponent, selector: "moz-grid-footer", inputs: ["pageSizeOptions"], outputs: ["pageChange"] }, { kind: "component", type: MozGridLoadingIndicatorComponent, selector: "moz-grid-loading-indicator" }, { kind: "component", type: MozGridSelectionBarComponent, selector: "moz-grid-selection-bar", outputs: ["editClick", "copyClick", "pasteClick", "deleteClick", "exportClick"] }, { kind: "component", type: MozTagComponent, selector: "moz-tag", inputs: ["type", "size", "id", "name", "disabled", "contextualisedNumber", "removableLabel"], outputs: ["removeTag"] }, { kind: "component", type: MozIconButtonComponent, selector: "moz-icon-button", inputs: ["id", "appearance", "size", "disabled", "ghost", "outlined", "type", "ariaLabel"], outputs: ["activated"] }, { kind: "component", type: ViewGridX420, selector: "ViewGridX420", inputs: ["hostClass"] }, { kind: "component", type: Filter20, selector: "Filter20", inputs: ["hostClass"] }, { kind: "component", type: Settings20, selector: "Settings20", inputs: ["hostClass"] }, { kind: "component", type: FullscreenEnter20, selector: "FullscreenEnter20", inputs: ["hostClass"] }, { kind: "component", type: FullscreenExit20, selector: "FullscreenExit20", inputs: ["hostClass"] }, { kind: "component", type: Download20, selector: "Download20", inputs: ["hostClass"] }, { kind: "component", type: ChevronUp20, selector: "ChevronUp20", inputs: ["hostClass"] }, { kind: "component", type: ChevronDown20, selector: "ChevronDown20", inputs: ["hostClass"] }, { kind: "component", type: Keyboard20, selector: "Keyboard20", inputs: ["hostClass"] }, { kind: "component", type: MozButtonComponent, selector: "button[moz-button]", inputs: ["appearance", "size", "disabled", "ghost", "outlined", "iconPosition", "type", "isLoading"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11987
+ `, isInline: true, styles: [":host{display:block;height:100%}.moz-grid-wrapper{display:flex;flex-direction:column;font-family:var(--font-family-primary);height:100%;min-height:0;gap:16px}.moz-grid-wrapper--fullscreen{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:var(--color-background-primary)}.moz-grid{display:flex;flex-direction:column;border-radius:var(--border-radius-l);overflow:hidden;background:var(--color-background-primary);flex:1;min-height:0;position:relative;box-shadow:0 0 6px #cdd4d8}.moz-grid__empty-overlay{position:absolute;inset:0;top:var(--moz-grid-header-height, 48px);display:flex;align-items:center;justify-content:center;background:var(--Background-Primary, #fff);z-index:1}.moz-grid:focus{outline:none}.moz-grid--loading{opacity:.6;pointer-events:none}.moz-grid__toolbar{display:flex;align-items:center;justify-content:space-between;flex-shrink:0;min-height:48px;gap:var(--spacing-s, 8px)}.moz-grid__toolbar-left,.moz-grid__toolbar-right{display:flex;align-items:center;gap:var(--spacing-xs, 4px)}.moz-grid__toolbar-filter-btn{display:inline-flex;align-items:center;gap:4px}.moz-grid__toolbar-filter-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 6px;border-radius:9px;background:var(--Status-Standalone-element-Primary, #0071ce);color:#fff;font-size:11px;font-weight:600;line-height:1}.moz-grid__selection-banner{display:flex;align-items:center;gap:var(--spacing-s, 8px);flex:1;justify-content:center;border-radius:var(--border-radius-s);border:1px solid var(--Border-Primary, #cdd4d8);background:var(--Neutral-Grey-000, #fff)}.moz-grid__selection-text{font-size:var(--font-size-s, 14px);color:var(--color-text-primary);white-space:nowrap}.moz-grid__selection-link{padding:0;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-s, 14px);font-weight:600;cursor:pointer;white-space:nowrap;text-decoration:underline}.moz-grid__selection-link:hover{color:var(--color-primary-dark, #1557b0)}.moz-grid__tag-bar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--spacing-xs, 4px);padding:var(--spacing-xxs, 2px) var(--spacing-s, 8px);flex-shrink:0}.moz-grid__tag-bar-label{width:100%;font-size:var(--font-size-xs, 12px);text-transform:uppercase;white-space:nowrap;color:var(--text-icon-tertiary);font-size:var(--Typography-Font-size-Body-XS, 12px);font-weight:400}.moz-grid__tag-action-btn{padding:2px 8px;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-xs, 12px);font-weight:600;cursor:pointer}.moz-grid__tag-action-btn:hover{text-decoration:underline}.moz-grid__group-tag-btn{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;cursor:pointer;font:inherit;color:inherit;line-height:1}.moz-grid__group-tag-btn ::ng-deep svg{fill:#fff!important;width:16px;height:16px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MozGridHeaderComponent, selector: "moz-grid-header", inputs: ["showCheckbox", "showExpand", "reorderable"], outputs: ["sortClick", "menuAction", "resizeStart", "selectAllToggle", "columnReorder"] }, { kind: "component", type: MozGridBodyComponent, selector: "moz-grid-body", inputs: ["showCheckbox", "showExpand", "detailTemplate"], outputs: ["cellEdit", "cellEditCancel", "rowSelectionToggle", "groupToggle"] }, { kind: "component", type: MozGridFooterComponent, selector: "moz-grid-footer", inputs: ["pageSizeOptions"], outputs: ["pageChange"] }, { kind: "component", type: MozGridLoadingIndicatorComponent, selector: "moz-grid-loading-indicator" }, { kind: "component", type: MozGridSelectionBarComponent, selector: "moz-grid-selection-bar", outputs: ["editClick", "copyClick", "pasteClick", "deleteClick", "exportClick"] }, { kind: "component", type: MozGridEmptyStateComponent, selector: "moz-grid-empty-state", inputs: ["kind", "title", "description", "actionLabel"], outputs: ["action"] }, { kind: "component", type: MozTagComponent, selector: "moz-tag", inputs: ["type", "size", "id", "name", "disabled", "contextualisedNumber", "removableLabel"], outputs: ["removeTag"] }, { kind: "component", type: MozIconButtonComponent, selector: "moz-icon-button", inputs: ["id", "appearance", "size", "disabled", "ghost", "outlined", "type", "ariaLabel"], outputs: ["activated"] }, { kind: "component", type: ViewGridX420, selector: "ViewGridX420", inputs: ["hostClass"] }, { kind: "component", type: Filter20, selector: "Filter20", inputs: ["hostClass"] }, { kind: "component", type: Settings20, selector: "Settings20", inputs: ["hostClass"] }, { kind: "component", type: FullscreenEnter20, selector: "FullscreenEnter20", inputs: ["hostClass"] }, { kind: "component", type: FullscreenExit20, selector: "FullscreenExit20", inputs: ["hostClass"] }, { kind: "component", type: Download20, selector: "Download20", inputs: ["hostClass"] }, { kind: "component", type: ChevronUp20, selector: "ChevronUp20", inputs: ["hostClass"] }, { kind: "component", type: ChevronDown20, selector: "ChevronDown20", inputs: ["hostClass"] }, { kind: "component", type: Keyboard20, selector: "Keyboard20", inputs: ["hostClass"] }, { kind: "component", type: MozButtonComponent, selector: "button[moz-button]", inputs: ["appearance", "size", "disabled", "ghost", "outlined", "iconPosition", "type", "isLoading"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11046
11988
  }
11047
11989
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridComponent, decorators: [{
11048
11990
  type: Component,
@@ -11077,6 +12019,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11077
12019
  MozGridFooterComponent,
11078
12020
  MozGridLoadingIndicatorComponent,
11079
12021
  MozGridSelectionBarComponent,
12022
+ MozGridEmptyStateComponent,
11080
12023
  MozTagComponent,
11081
12024
  MozIconButtonComponent,
11082
12025
  ViewGridX420,
@@ -11119,15 +12062,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11119
12062
  <Download20 icon />
11120
12063
  </moz-icon-button>
11121
12064
  }
11122
- <moz-icon-button
12065
+ <button
12066
+ type="button"
12067
+ moz-button
11123
12068
  id="grid-filter"
11124
12069
  size="s"
11125
12070
  [ghost]="true"
11126
- ariaLabel="Filter"
11127
- (activated)="filterPanelOpen.set(!filterPanelOpen())"
12071
+ [ariaLabel]="'Filters'"
12072
+ class="moz-grid__toolbar-filter-btn"
12073
+ (click)="onFiltersClick()"
11128
12074
  >
11129
12075
  <Filter20 icon />
11130
- </moz-icon-button>
12076
+ <span>Filters</span>
12077
+ @if (activeFilterCount() > 0) {
12078
+ <span class="moz-grid__toolbar-filter-badge" aria-hidden="true">
12079
+ {{ activeFilterCount() }}
12080
+ </span>
12081
+ }
12082
+ </button>
11131
12083
  <moz-icon-button
11132
12084
  id="grid-settings"
11133
12085
  size="s"
@@ -11237,15 +12189,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11237
12189
  </button>
11238
12190
  }
11239
12191
  </div>
11240
- } @if (state.activeFilters().length > 0) {
12192
+ } @if (activeFilters().length > 0) {
11241
12193
  <div class="moz-grid__tag-bar">
11242
12194
  <span class="moz-grid__tag-bar-label">FILTERED BY</span>
11243
- @for (filter of state.activeFilters(); track filter.field) {
12195
+ @for (filter of activeFilters(); track filter.id) {
11244
12196
  <moz-tag
11245
12197
  [type]="filter.removable ? 'removable' : 'informative'"
11246
12198
  size="s"
11247
- [id]="'filter-' + filter.field"
11248
- (removeTag)="onRemoveFilter(filter.field)"
12199
+ [id]="'filter-' + filter.id"
12200
+ (removeTag)="onRemoveFilter(filter.id)"
11249
12201
  >{{ filter.label }}</moz-tag
11250
12202
  >
11251
12203
  }
@@ -11287,6 +12239,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11287
12239
  (rowSelectionToggle)="onRowSelectionToggle()"
11288
12240
  />
11289
12241
 
12242
+ <!-- Empty state overlay (consumer template wins, fallback default) -->
12243
+ @if (emptyStateKind() !== 'none') {
12244
+ <div class="moz-grid__empty-overlay">
12245
+ @if (emptyTemplate(); as tpl) {
12246
+ <ng-container *ngTemplateOutlet="tpl; context: emptyTemplateContext()" />
12247
+ } @else {
12248
+ <moz-grid-empty-state
12249
+ [kind]="$any(emptyStateKind())"
12250
+ [title]="emptyStateKind() === 'no-results' ? noResultsTitle() : emptyDataTitle()"
12251
+ [description]="
12252
+ emptyStateKind() === 'no-results' ? noResultsDescription() : emptyDataDescription()
12253
+ "
12254
+ [actionLabel]="emptyStateKind() === 'no-results' ? resolvedNoResultsActionLabel() : ''"
12255
+ (action)="onRemoveAllFilters()"
12256
+ />
12257
+ }
12258
+ </div>
12259
+ }
12260
+
11290
12261
  <!-- Footer: pagination or infinite scroll loading indicator -->
11291
12262
  @if (showPagination()) {
11292
12263
  <moz-grid-footer
@@ -11307,8 +12278,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11307
12278
  />
11308
12279
  </div>
11309
12280
  </div>
11310
- `, styles: [":host{display:block;height:100%}.moz-grid-wrapper{display:flex;flex-direction:column;font-family:var(--font-family-primary);height:100%;min-height:0;gap:16px}.moz-grid-wrapper--fullscreen{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:var(--color-background-primary)}.moz-grid{display:flex;flex-direction:column;border-radius:var(--border-radius-l);overflow:hidden;background:var(--color-background-primary);flex:1;min-height:0;position:relative;box-shadow:0 0 6px #cdd4d8}.moz-grid:focus{outline:none}.moz-grid--loading{opacity:.6;pointer-events:none}.moz-grid__toolbar{display:flex;align-items:center;justify-content:space-between;flex-shrink:0;min-height:48px;gap:var(--spacing-s, 8px)}.moz-grid__toolbar-left,.moz-grid__toolbar-right{display:flex;align-items:center;gap:var(--spacing-xs, 4px)}.moz-grid__selection-banner{display:flex;align-items:center;gap:var(--spacing-s, 8px);flex:1;justify-content:center;border-radius:var(--border-radius-s);border:1px solid var(--Border-Primary, #cdd4d8);background:var(--Neutral-Grey-000, #fff)}.moz-grid__selection-text{font-size:var(--font-size-s, 14px);color:var(--color-text-primary);white-space:nowrap}.moz-grid__selection-link{padding:0;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-s, 14px);font-weight:600;cursor:pointer;white-space:nowrap;text-decoration:underline}.moz-grid__selection-link:hover{color:var(--color-primary-dark, #1557b0)}.moz-grid__tag-bar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--spacing-xs, 4px);padding:var(--spacing-xxs, 2px) var(--spacing-s, 8px);flex-shrink:0}.moz-grid__tag-bar-label{width:100%;font-size:var(--font-size-xs, 12px);text-transform:uppercase;white-space:nowrap;color:var(--text-icon-tertiary);font-size:var(--Typography-Font-size-Body-XS, 12px);font-weight:400}.moz-grid__tag-action-btn{padding:2px 8px;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-xs, 12px);font-weight:600;cursor:pointer}.moz-grid__tag-action-btn:hover{text-decoration:underline}.moz-grid__group-tag-btn{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;cursor:pointer;font:inherit;color:inherit;line-height:1}.moz-grid__group-tag-btn ::ng-deep svg{fill:#fff!important;width:16px;height:16px}\n"] }]
11311
- }], ctorParameters: () => [], propDecorators: { gridBody: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MozGridBodyComponent), { isSignal: true }] }], gridContainer: [{ type: i0.ViewChild, args: ['gridContainer', { isSignal: true }] }], columnDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MozGridColumnDef), { isSignal: true }] }], toolbarDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MozGridToolbarDef), { isSignal: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], rowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowSelection", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], rowIdField: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIdField", required: false }] }], detailTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailTemplate", required: false }] }], fullscreen: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullscreen", required: false }] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], stateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateKey", required: false }] }], exportable: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportable", required: false }] }], horizontalVirtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "horizontalVirtualScroll", required: false }] }], loadingStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingStrategy", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], plugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "plugins", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], loadMore: [{ type: i0.Output, args: ["loadMore"] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], cellEditCancel: [{ type: i0.Output, args: ["cellEditCancel"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], cellSelectionChange: [{ type: i0.Output, args: ["cellSelectionChange"] }], groupChange: [{ type: i0.Output, args: ["groupChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], bulkEdit: [{ type: i0.Output, args: ["bulkEdit"] }], bulkCopy: [{ type: i0.Output, args: ["bulkCopy"] }], bulkPaste: [{ type: i0.Output, args: ["bulkPaste"] }], bulkDelete: [{ type: i0.Output, args: ["bulkDelete"] }], fillDown: [{ type: i0.Output, args: ["fillDown"] }], settingsChange: [{ type: i0.Output, args: ["settingsChange"] }] } });
12281
+ `, styles: [":host{display:block;height:100%}.moz-grid-wrapper{display:flex;flex-direction:column;font-family:var(--font-family-primary);height:100%;min-height:0;gap:16px}.moz-grid-wrapper--fullscreen{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:var(--color-background-primary)}.moz-grid{display:flex;flex-direction:column;border-radius:var(--border-radius-l);overflow:hidden;background:var(--color-background-primary);flex:1;min-height:0;position:relative;box-shadow:0 0 6px #cdd4d8}.moz-grid__empty-overlay{position:absolute;inset:0;top:var(--moz-grid-header-height, 48px);display:flex;align-items:center;justify-content:center;background:var(--Background-Primary, #fff);z-index:1}.moz-grid:focus{outline:none}.moz-grid--loading{opacity:.6;pointer-events:none}.moz-grid__toolbar{display:flex;align-items:center;justify-content:space-between;flex-shrink:0;min-height:48px;gap:var(--spacing-s, 8px)}.moz-grid__toolbar-left,.moz-grid__toolbar-right{display:flex;align-items:center;gap:var(--spacing-xs, 4px)}.moz-grid__toolbar-filter-btn{display:inline-flex;align-items:center;gap:4px}.moz-grid__toolbar-filter-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 6px;border-radius:9px;background:var(--Status-Standalone-element-Primary, #0071ce);color:#fff;font-size:11px;font-weight:600;line-height:1}.moz-grid__selection-banner{display:flex;align-items:center;gap:var(--spacing-s, 8px);flex:1;justify-content:center;border-radius:var(--border-radius-s);border:1px solid var(--Border-Primary, #cdd4d8);background:var(--Neutral-Grey-000, #fff)}.moz-grid__selection-text{font-size:var(--font-size-s, 14px);color:var(--color-text-primary);white-space:nowrap}.moz-grid__selection-link{padding:0;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-s, 14px);font-weight:600;cursor:pointer;white-space:nowrap;text-decoration:underline}.moz-grid__selection-link:hover{color:var(--color-primary-dark, #1557b0)}.moz-grid__tag-bar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--spacing-xs, 4px);padding:var(--spacing-xxs, 2px) var(--spacing-s, 8px);flex-shrink:0}.moz-grid__tag-bar-label{width:100%;font-size:var(--font-size-xs, 12px);text-transform:uppercase;white-space:nowrap;color:var(--text-icon-tertiary);font-size:var(--Typography-Font-size-Body-XS, 12px);font-weight:400}.moz-grid__tag-action-btn{padding:2px 8px;border:none;background:transparent;color:var(--color-background-accent-inverse);font-size:var(--font-size-xs, 12px);font-weight:600;cursor:pointer}.moz-grid__tag-action-btn:hover{text-decoration:underline}.moz-grid__group-tag-btn{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;cursor:pointer;font:inherit;color:inherit;line-height:1}.moz-grid__group-tag-btn ::ng-deep svg{fill:#fff!important;width:16px;height:16px}\n"] }]
12282
+ }], ctorParameters: () => [], propDecorators: { gridBody: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MozGridBodyComponent), { isSignal: true }] }], gridContainer: [{ type: i0.ViewChild, args: ['gridContainer', { isSignal: true }] }], columnDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MozGridColumnDef), { isSignal: true }] }], toolbarDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MozGridToolbarDef), { isSignal: true }] }], emptyDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MozGridEmptyDef), { isSignal: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], rowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowSelection", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], rowIdField: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIdField", required: false }] }], detailTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailTemplate", required: false }] }], fullscreen: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullscreen", required: false }] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], stateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateKey", required: false }] }], emptyDataTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyDataTitle", required: false }] }], emptyDataDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyDataDescription", required: false }] }], noResultsTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsTitle", required: false }] }], noResultsDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsDescription", required: false }] }], noResultsActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsActionLabel", required: false }] }], exportable: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportable", required: false }] }], horizontalVirtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "horizontalVirtualScroll", required: false }] }], loadingStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingStrategy", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], plugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "plugins", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], loadMore: [{ type: i0.Output, args: ["loadMore"] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], cellEditCancel: [{ type: i0.Output, args: ["cellEditCancel"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], cellSelectionChange: [{ type: i0.Output, args: ["cellSelectionChange"] }], groupChange: [{ type: i0.Output, args: ["groupChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], bulkEdit: [{ type: i0.Output, args: ["bulkEdit"] }], bulkCopy: [{ type: i0.Output, args: ["bulkCopy"] }], bulkPaste: [{ type: i0.Output, args: ["bulkPaste"] }], bulkDelete: [{ type: i0.Output, args: ["bulkDelete"] }], fillDown: [{ type: i0.Output, args: ["fillDown"] }], settingsChange: [{ type: i0.Output, args: ["settingsChange"] }], filterApplyMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterApplyMode", required: false }] }] } });
11312
12283
 
11313
12284
  const DEFAULT_GRID_OPTIONS = {
11314
12285
  mode: 'client',
@@ -12924,5 +13895,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
12924
13895
  * Generated bundle index. Do not edit.
12925
13896
  */
12926
13897
 
12927
- export { ACTION_LISTBOX_CONFIG, ActionListboxContainerComponent, ActionListboxRef, BuiltInMenuComponent, CellSelectionEngine, CellValidationEngine, ColumnReorderEngine, ColumnResizeEngine, DEFAULT_ACTION_LISTBOX_CONFIG, DEFAULT_GRID_OPTIONS, DEFAULT_MODAL_CONFIG, DEFAULT_TOASTER_CONFIG, DRAWER_CONFIG, DRAWER_DATA, DrawerContainerComponent, ExpandableRowEngine, ExportEngine, FilterEngine, GridEngine, GridGroupDrawerComponent, GridSettingsDrawerComponent, GridStateManager, GroupEngine, HorizontalVirtualScrollEngine, InfiniteScrollEngine, InlineEditEngine, KeyboardEngine, MODAL_CONFIG, MODAL_DATA, MozAccordionComponent, MozAccordionContentComponent, MozAccordionHeaderComponent, MozAccordionPanelComponent, MozActionBottomBarComponent, MozActionListboxComponent, MozActionListboxTriggerDirective, MozAvatarComponent, MozBreadcrumbComponent, MozButtonComponent, MozCalloutComponent, MozCarouselComponent, MozCheckListMenuComponent, MozCheckboxComponent, MozCheckboxGroupComponent, MozCircularProgressBarComponent, MozComboboxComponent, MozComboboxHarness, MozComboboxOptionHarness, MozDatepickerComponent, MozDividerComponent, MozDrawerComponent, MozDrawerFooterDirective, MozDrawerRef, MozDrawerService, MozFieldComponent, MozFieldGroupComponent, MozFileUploaderComponent, MozFileUploaderItemComponent, MozFlagComponent, MozGridBodyComponent, MozGridCellComponent, MozGridColumnDef, MozGridColumnVisibilityPanelComponent, MozGridComponent, MozGridDetailRowComponent, MozGridFooterComponent, MozGridGroupRowComponent, MozGridHeaderCellComponent, MozGridHeaderComponent, MozGridHeaderMenuComponent, MozGridLoadingIndicatorComponent, MozGridRowComponent, MozGridToolbarDef, MozIconButtonComponent, MozKpiComponent, MozLinearProgressBarBufferComponent, MozLinearProgressBarPercentageComponent, MozLinkComponent, MozLoaderComponent, MozLoadingOverlayComponent, MozModalComponent, MozModalFooterDirective, MozModalRef, MozModalService, MozNavigationIndicatorComponent, MozNumberBadgeComponent, MozOverlayComponent, MozPageHeaderComponent, MozPaginationComponent, MozPasswordInputDirective, MozPhoneNumberComponent, MozPincodeInputComponent, MozPopoverComponent, MozPopoverFooterDirective, MozPopoverTriggerDirective, MozQuantitySelectorComponent, MozRadioComponent, MozRadioGroupComponent, MozSegmentedControlComponent, MozSelectComponent, MozSidebarComponent, MozStarRatingComponent, MozStatusBadgeComponent, MozStatusDotComponent, MozStatusMessageComponent, MozStatusNotificationComponent, MozStepperBottomBarComponent, MozStepperCompactComponent, MozStepperInlineComponent, MozStepperStackedComponent, MozTabComponent, MozTabsComponent, MozTagComponent, MozTextInput, MozTextarea, MozTileComponent, MozTileExpandableComponent, MozTileSelectableComponent, MozToasterComponent, MozToasterRef, MozToasterService, MozToggleComponent, MozTooltipComponent, MozTooltipDirective, MozTreeComponent, MozTreeNodeComponent, MozTreeNodeTemplateDirective, POPOVER_CONFIG, POPOVER_DATA, PaginationEngine, PopoverContainerComponent, PopoverRef, PopoverService, RowSelectionEngine, SortEngine, StatePersistenceEngine, TOASTER_CONFIG, TreeEngine, TreeKeyboardService, TreeSelectionService, TreeStateService, isSection, trackByField, trackDisplayRow };
13898
+ export { ACTION_LISTBOX_CONFIG, ActionListboxContainerComponent, ActionListboxRef, BuiltInMenuComponent, CellSelectionEngine, CellValidationEngine, ColumnReorderEngine, ColumnResizeEngine, DEFAULT_ACTION_LISTBOX_CONFIG, DEFAULT_GRID_OPTIONS, DEFAULT_MODAL_CONFIG, DEFAULT_TOASTER_CONFIG, DRAWER_CONFIG, DRAWER_DATA, DrawerContainerComponent, ExpandableRowEngine, ExportEngine, FilterEngine, GridEngine, GridGroupDrawerComponent, GridSettingsDrawerComponent, GridStateManager, GroupEngine, HorizontalVirtualScrollEngine, InfiniteScrollEngine, InlineEditEngine, KeyboardEngine, MODAL_CONFIG, MODAL_DATA, MozAccordionComponent, MozAccordionContentComponent, MozAccordionHeaderComponent, MozAccordionPanelComponent, MozActionBottomBarComponent, MozActionListboxComponent, MozActionListboxTriggerDirective, MozAvatarComponent, MozBreadcrumbComponent, MozButtonComponent, MozCalloutComponent, MozCarouselComponent, MozCheckListMenuComponent, MozCheckboxComponent, MozCheckboxGroupComponent, MozCircularProgressBarComponent, MozComboboxComponent, MozComboboxHarness, MozComboboxOptionHarness, MozDatepickerComponent, MozDividerComponent, MozDrawerComponent, MozDrawerFooterDirective, MozDrawerRef, MozDrawerService, MozFieldComponent, MozFieldGroupComponent, MozFileUploaderComponent, MozFileUploaderItemComponent, MozFlagComponent, MozGridBodyComponent, MozGridCellComponent, MozGridColumnDef, MozGridColumnVisibilityPanelComponent, MozGridComponent, MozGridDetailRowComponent, MozGridEmptyDef, MozGridFooterComponent, MozGridGroupRowComponent, MozGridHeaderCellComponent, MozGridHeaderComponent, MozGridHeaderMenuComponent, MozGridLoadingIndicatorComponent, MozGridRowComponent, MozGridToolbarDef, MozIconButtonComponent, MozKpiComponent, MozLinearProgressBarBufferComponent, MozLinearProgressBarPercentageComponent, MozLinkComponent, MozLoaderComponent, MozLoadingOverlayComponent, MozModalComponent, MozModalFooterDirective, MozModalRef, MozModalService, MozNavigationIndicatorComponent, MozNumberBadgeComponent, MozOverlayComponent, MozPageHeaderComponent, MozPaginationComponent, MozPasswordInputDirective, MozPhoneNumberComponent, MozPincodeInputComponent, MozPopoverComponent, MozPopoverFooterDirective, MozPopoverTriggerDirective, MozQuantitySelectorComponent, MozRadioComponent, MozRadioGroupComponent, MozSegmentedControlComponent, MozSelectComponent, MozSidebarComponent, MozStarRatingComponent, MozStatusBadgeComponent, MozStatusDotComponent, MozStatusMessageComponent, MozStatusNotificationComponent, MozStepperBottomBarComponent, MozStepperCompactComponent, MozStepperInlineComponent, MozStepperStackedComponent, MozTabComponent, MozTabsComponent, MozTagComponent, MozTextInput, MozTextarea, MozTileComponent, MozTileExpandableComponent, MozTileSelectableComponent, MozToasterComponent, MozToasterRef, MozToasterService, MozToggleComponent, MozTooltipComponent, MozTooltipDirective, MozTreeComponent, MozTreeNodeComponent, MozTreeNodeTemplateDirective, POPOVER_CONFIG, POPOVER_DATA, PaginationEngine, PopoverContainerComponent, PopoverRef, PopoverService, RowSelectionEngine, SortEngine, StatePersistenceEngine, TOASTER_CONFIG, TreeEngine, TreeKeyboardService, TreeSelectionService, TreeStateService, isSection, trackByField, trackDisplayRow };
12928
13899
  //# sourceMappingURL=mozaic-ds-angular.mjs.map