@mozaic-ds/angular 2.0.44 → 2.0.45

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, 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,272 @@ 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
+ class MozGridFilterBuilderComponent {
8505
+ model = input.required(...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
8506
+ availableColumns = input.required(...(ngDevMode ? [{ debugName: "availableColumns" }] : /* istanbul ignore next */ []));
8507
+ applyMode = input('auto', ...(ngDevMode ? [{ debugName: "applyMode" }] : /* istanbul ignore next */ []));
8508
+ /** Hint: when true, renders a "Show rows" sub-title (used inside the overlay). */
8509
+ showSubtitle = input(true, ...(ngDevMode ? [{ debugName: "showSubtitle" }] : /* istanbul ignore next */ []));
8510
+ modelChange = output();
8511
+ // Draft (what the user sees). Synced from `model` input on change.
8512
+ draft = signal([], ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
8513
+ constructor() {
8514
+ effect(() => {
8515
+ const incoming = this.model().conditions;
8516
+ // Avoid clobbering local mutations: only sync when the incoming model
8517
+ // differs by id-set or values from the current draft.
8518
+ const current = untracked(() => this.draft());
8519
+ if (!conditionsEqual(incoming, current)) {
8520
+ this.draft.set(incoming.map((c) => ({ ...c, value: { ...c.value } })));
8521
+ }
8522
+ });
8523
+ }
8524
+ columnsById = computed(() => {
8525
+ const m = new Map();
8526
+ for (const c of this.availableColumns())
8527
+ m.set(c.field, c);
8528
+ return m;
8529
+ }, ...(ngDevMode ? [{ debugName: "columnsById" }] : /* istanbul ignore next */ []));
8530
+ operatorLabels = OPERATOR_LABELS;
8531
+ // ------------------------------------------------------------------
8532
+ // Mutations
8533
+ // ------------------------------------------------------------------
8534
+ addCondition() {
8535
+ const cols = this.availableColumns();
8536
+ if (cols.length === 0)
8537
+ return;
8538
+ const first = cols[0];
8539
+ const isFirst = this.draft().length === 0;
8540
+ const condition = {
8541
+ id: generateConditionId(),
8542
+ combinator: isFirst ? 'and' : 'and',
8543
+ field: first.field,
8544
+ operator: first.defaultOperator,
8545
+ value: {},
8546
+ };
8547
+ this.draft.update((list) => [...list, condition]);
8548
+ this.commit();
8549
+ }
8550
+ removeCondition(id) {
8551
+ this.draft.update((list) => list.filter((c) => c.id !== id));
8552
+ this.commit();
8553
+ }
8554
+ onCombinatorChange(id, combinator) {
8555
+ this.draft.update((list) => list.map((c) => (c.id === id ? { ...c, combinator } : c)));
8556
+ this.commit();
8557
+ }
8558
+ onFieldChange(id, field) {
8559
+ const col = this.columnsById().get(field);
8560
+ this.draft.update((list) => list.map((c) => c.id === id
8561
+ ? {
8562
+ ...c,
8563
+ field,
8564
+ operator: col?.defaultOperator ?? c.operator,
8565
+ value: {},
8566
+ }
8567
+ : c));
8568
+ this.commit();
8569
+ }
8570
+ onOperatorChange(id, operator) {
8571
+ this.draft.update((list) => list.map((c) => (c.id === id ? { ...c, operator, value: resetValueFor(operator, c.value) } : c)));
8572
+ this.commit();
8573
+ }
8574
+ onValueChange(id, patch) {
8575
+ this.draft.update((list) => list.map((c) => c.id === id
8576
+ ? {
8577
+ ...c,
8578
+ value: { ...c.value, ...patch },
8579
+ }
8580
+ : c));
8581
+ this.commit();
8582
+ }
8583
+ onSetValueChange(id, event) {
8584
+ const select = event.target;
8585
+ const values = Array.from(select.selectedOptions).map((o) => o.value);
8586
+ this.onValueChange(id, { value: values });
8587
+ }
8588
+ onDrop(event) {
8589
+ this.draft.update((list) => {
8590
+ const next = [...list];
8591
+ moveItemInArray(next, event.previousIndex, event.currentIndex);
8592
+ return next;
8593
+ });
8594
+ this.commit();
8595
+ }
8596
+ // ------------------------------------------------------------------
8597
+ // Value editor helpers (for the template)
8598
+ // ------------------------------------------------------------------
8599
+ needsValue(op) {
8600
+ return !VALUELESS_OPERATORS.has(op);
8601
+ }
8602
+ needsRange(op) {
8603
+ return RANGE_OPERATORS.has(op);
8604
+ }
8605
+ getSelectedSetValues(condition) {
8606
+ const v = condition.value.value;
8607
+ if (Array.isArray(v))
8608
+ return v.map((x) => String(x));
8609
+ if (v == null || v === '')
8610
+ return [];
8611
+ return [String(v)];
8612
+ }
8613
+ isSetValueSelected(condition, value) {
8614
+ return this.getSelectedSetValues(condition).includes(String(value));
8615
+ }
8616
+ inputTypeFor(op, type) {
8617
+ if (type === 'number')
8618
+ return 'number';
8619
+ if (type === 'date')
8620
+ return 'date';
8621
+ return 'text';
8622
+ }
8623
+ // ------------------------------------------------------------------
8624
+ // Internals
8625
+ // ------------------------------------------------------------------
8626
+ commit() {
8627
+ this.modelChange.emit({ conditions: this.draft().map((c) => ({ ...c, value: { ...c.value } })) });
8628
+ }
8629
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8630
+ 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;min-width:560px;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 });
8631
+ }
8632
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterBuilderComponent, decorators: [{
8633
+ type: Component,
8634
+ args: [{ selector: 'moz-grid-filter-builder', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
8635
+ FormsModule,
8636
+ CdkDropList,
8637
+ CdkDrag,
8638
+ CdkDragHandle,
8639
+ MozButtonComponent,
8640
+ Drag20,
8641
+ Cross20,
8642
+ ListAdd20,
8643
+ ], 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;min-width:560px;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"] }]
8644
+ }], 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"] }] } });
8645
+ function resetValueFor(op, previous) {
8646
+ if (VALUELESS_OPERATORS.has(op))
8647
+ return {};
8648
+ if (RANGE_OPERATORS.has(op))
8649
+ return { value: previous.value ?? '', valueTo: previous.valueTo ?? '' };
8650
+ return { value: previous.value ?? '' };
8651
+ }
8652
+ function conditionsEqual(a, b) {
8653
+ if (a.length !== b.length)
8654
+ return false;
8655
+ for (let i = 0; i < a.length; i++) {
8656
+ const ca = a[i];
8657
+ const cb = b[i];
8658
+ if (ca.id !== cb.id ||
8659
+ ca.combinator !== cb.combinator ||
8660
+ ca.field !== cb.field ||
8661
+ ca.operator !== cb.operator ||
8662
+ ca.value.value !== cb.value.value ||
8663
+ ca.value.valueTo !== cb.value.valueTo) {
8664
+ return false;
8665
+ }
8666
+ }
8667
+ return true;
8668
+ }
8669
+
8670
+ /**
8671
+ * Programmatically opens a CDK overlay anchored on the host element that
8672
+ * renders the filter builder. Unlike the action-listbox directive, the
8673
+ * overlay does not toggle on click — the host is simply the anchor. Open
8674
+ * the overlay by injecting this directive via a template ref (`#filter`)
8675
+ * and calling `filter.open(options)`.
8676
+ */
8677
+ class MozGridFilterOverlayDirective {
8678
+ overlay = inject(Overlay);
8679
+ host = inject((ElementRef));
8680
+ injector = inject(Injector);
8681
+ engine = inject(FilterEngine, { optional: true });
8682
+ overlayRef = null;
8683
+ componentRef = null;
8684
+ /** Opens the overlay anchored on the host. No-op if already open. */
8685
+ open(options) {
8686
+ if (this.overlayRef)
8687
+ return;
8688
+ if (!this.engine)
8689
+ return;
8690
+ const positions = [
8691
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
8692
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
8693
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
8694
+ ];
8695
+ const positionStrategy = this.overlay
8696
+ .position()
8697
+ .flexibleConnectedTo(this.host)
8698
+ .withPositions(positions)
8699
+ .withPush(true)
8700
+ .withViewportMargin(8);
8701
+ const config = new OverlayConfig({
8702
+ hasBackdrop: true,
8703
+ backdropClass: 'cdk-overlay-transparent-backdrop',
8704
+ panelClass: 'moz-grid-filter-overlay',
8705
+ positionStrategy,
8706
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
8707
+ });
8708
+ const overlayRef = this.overlay.create(config);
8709
+ this.overlayRef = overlayRef;
8710
+ // Seed the draft with a new condition targeting the clicked column, if any
8711
+ const seededModel = options.seedField && options.model.conditions.length === 0
8712
+ ? { conditions: [this.engine.makeCondition(options.seedField, true)] }
8713
+ : options.model;
8714
+ const portal = new ComponentPortal(MozGridFilterBuilderComponent, null, this.injector);
8715
+ const compRef = overlayRef.attach(portal);
8716
+ compRef.setInput('model', seededModel);
8717
+ compRef.setInput('availableColumns', options.columns);
8718
+ compRef.setInput('applyMode', 'auto');
8719
+ compRef.setInput('showSubtitle', true);
8720
+ compRef.instance.modelChange.subscribe((next) => {
8721
+ compRef.setInput('model', next);
8722
+ options.onChange(next);
8723
+ });
8724
+ this.componentRef = compRef;
8725
+ overlayRef.backdropClick().subscribe(() => this.close());
8726
+ overlayRef.keydownEvents().subscribe((event) => {
8727
+ if (event.key === 'Escape')
8728
+ this.close();
8729
+ });
8730
+ }
8731
+ close() {
8732
+ this.overlayRef?.dispose();
8733
+ this.overlayRef = null;
8734
+ this.componentRef = null;
8735
+ }
8736
+ ngOnDestroy() {
8737
+ this.close();
8738
+ }
8739
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8740
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: MozGridFilterOverlayDirective, isStandalone: true, selector: "[mozGridFilterOverlay]", exportAs: ["mozGridFilterOverlay"], ngImport: i0 });
8741
+ }
8742
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterOverlayDirective, decorators: [{
8743
+ type: Directive,
8744
+ args: [{
8745
+ selector: '[mozGridFilterOverlay]',
8746
+ exportAs: 'mozGridFilterOverlay',
8747
+ }]
8748
+ }] });
8749
+
8150
8750
  class MozGridHeaderCellComponent {
8151
8751
  state = inject(GridStateManager);
8752
+ filterEngine = inject(FilterEngine);
8152
8753
  columnState = input.required(...(ngDevMode ? [{ debugName: "columnState" }] : /* istanbul ignore next */ []));
8153
8754
  def = input.required(...(ngDevMode ? [{ debugName: "def" }] : /* istanbul ignore next */ []));
8154
8755
  isLast = input(false, ...(ngDevMode ? [{ debugName: "isLast" }] : /* istanbul ignore next */ []));
8155
8756
  pinnedEnd = input(false, ...(ngDevMode ? [{ debugName: "pinnedEnd" }] : /* istanbul ignore next */ []));
8156
8757
  reorderable = input(false, ...(ngDevMode ? [{ debugName: "reorderable" }] : /* istanbul ignore next */ []));
8758
+ filterOverlay = viewChild(MozGridFilterOverlayDirective, ...(ngDevMode ? [{ debugName: "filterOverlay" }] : /* istanbul ignore next */ []));
8759
+ /** True when at least one active condition targets this column. */
8760
+ hasActiveFilter = computed(() => this.filterEngine.conditions().some((c) => c.field === this.columnState().field), ...(ngDevMode ? [{ debugName: "hasActiveFilter" }] : /* istanbul ignore next */ []));
8761
+ /** Tooltip for the gear / filter button (count + short summary). */
8762
+ filterTooltip = computed(() => {
8763
+ const field = this.columnState().field;
8764
+ const matching = this.filterEngine.conditions().filter((c) => c.field === field);
8765
+ if (matching.length === 0)
8766
+ return 'Column settings';
8767
+ const summary = matching.map((c) => this.filterEngine.toLabel(c)).join(', ');
8768
+ return `${matching.length} filter${matching.length > 1 ? 's' : ''}: ${summary}`;
8769
+ }, ...(ngDevMode ? [{ debugName: "filterTooltip" }] : /* istanbul ignore next */ []));
8157
8770
  resolvedMinWidth = computed(() => {
8158
8771
  const def = this.def();
8159
8772
  return def.minWidth ? parseInt(def.minWidth, 10) || 50 : 50;
@@ -8175,6 +8788,14 @@ class MozGridHeaderCellComponent {
8175
8788
  if (def.sortable !== false) {
8176
8789
  items.push({ id: 'sort-asc', icon: ChevronUp20, label: 'Sort A → Z' }, { id: 'sort-desc', icon: ChevronDown20, label: 'Sort Z → A' });
8177
8790
  }
8791
+ if (def.filterable) {
8792
+ items.push({
8793
+ id: 'filter-column',
8794
+ label: 'Filter in this column',
8795
+ icon: Filter20,
8796
+ divider: items.length > 0,
8797
+ });
8798
+ }
8178
8799
  if (def.groupable) {
8179
8800
  items.push({
8180
8801
  id: 'group-column',
@@ -8220,24 +8841,58 @@ class MozGridHeaderCellComponent {
8220
8841
  }
8221
8842
  }
8222
8843
  onMenuItemClick(item) {
8223
- this.menuAction.emit({
8224
- field: this.columnState().field,
8225
- actionId: item.id,
8226
- });
8844
+ const field = this.columnState().field;
8845
+ const actionId = item.id;
8846
+ // Intercept "Filter in this column": when the column does not provide a
8847
+ // custom `filterTemplate`, open the overlay anchored on the gear button
8848
+ // instead of delegating to the grid shell (which would toggle the legacy
8849
+ // per-column search input). Columns that ship a filterTemplate keep their
8850
+ // legacy behaviour — the grid handles them via `onMenuAction`.
8851
+ if (actionId === 'filter-column' && !this.def().filterTemplate) {
8852
+ this.openFilterOverlay(field);
8853
+ return;
8854
+ }
8855
+ this.menuAction.emit({ field, actionId });
8856
+ }
8857
+ openFilterOverlay(seedField) {
8858
+ // Defer so the action-listbox has time to tear down its overlay before
8859
+ // we attach a new one at roughly the same location. Without this, the
8860
+ // lingering mouseup from the menu-item click can land on the freshly
8861
+ // mounted filter backdrop and close it instantly.
8862
+ setTimeout(() => {
8863
+ const overlay = this.filterOverlay();
8864
+ if (!overlay)
8865
+ return;
8866
+ overlay.open({
8867
+ columns: this.filterEngine.describeFilterableColumns(),
8868
+ model: { conditions: this.filterEngine.conditions().slice() },
8869
+ seedField,
8870
+ onChange: (next) => {
8871
+ this.filterEngine.setModel(next, 'replace');
8872
+ },
8873
+ });
8874
+ }, 0);
8227
8875
  }
8228
8876
  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 });
8877
+ 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
8878
  }
8231
8879
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridHeaderCellComponent, decorators: [{
8232
8880
  type: Component,
8233
- args: [{ selector: 'moz-grid-header-cell', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChevronDown20, ChevronUp20, Settings20, MozActionListboxTriggerDirective], host: {
8881
+ args: [{ selector: 'moz-grid-header-cell', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
8882
+ ChevronDown20,
8883
+ ChevronUp20,
8884
+ Settings20,
8885
+ Filter20,
8886
+ MozActionListboxTriggerDirective,
8887
+ MozGridFilterOverlayDirective,
8888
+ ], host: {
8234
8889
  '[style.flex]': 'isLast() ? "1 0 auto" : "0 0 auto"',
8235
8890
  '[style.width.px]': 'isLast() ? undefined : columnState().currentWidth',
8236
8891
  '[style.min-width.px]': 'isLast() ? columnState().currentWidth : resolvedMinWidth()',
8237
8892
  '[class.grid-header-cell-host--dragging]': 'isDragging()',
8238
8893
  '[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"] }] } });
8894
+ }, 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"] }]
8895
+ }], 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
8896
 
8242
8897
  function trackDisplayRow(_index, row) {
8243
8898
  if (row.type === 'group') {
@@ -8490,6 +9145,12 @@ class AutoSizeVirtualScrollStrategy {
8490
9145
  const viewportSize = this._viewport.getViewportSize();
8491
9146
  const dataLength = this._viewport.getDataLength();
8492
9147
  if (dataLength === 0) {
9148
+ // IMPORTANT: also reset the cache. Otherwise, when data comes back to
9149
+ // the same length as before the empty state (e.g. filter matching N,
9150
+ // then 0, then N again), the range we compute below equals the stale
9151
+ // `_lastRenderedRange` and `setRenderedRange` is skipped — the viewport
9152
+ // stays stuck on empty until a scroll event forces a re-render.
9153
+ this._lastRenderedRange = { start: 0, end: 0 };
8493
9154
  this._viewport.setRenderedRange({ start: 0, end: 0 });
8494
9155
  this._viewport.setRenderedContentOffset(0);
8495
9156
  return;
@@ -8637,8 +9298,9 @@ class AutoSizeVirtualScrollStrategy {
8637
9298
  // Quick check: if the first child matches the default height,
8638
9299
  // assume all rows are uniform and skip the full measurement pass.
8639
9300
  const firstHeight = children[0].offsetHeight;
8640
- if (firstHeight > 0 && Math.abs(firstHeight - this._defaultItemSize) <= 0.5
8641
- && this._heightMap.size === 0) {
9301
+ if (firstHeight > 0 &&
9302
+ Math.abs(firstHeight - this._defaultItemSize) <= 0.5 &&
9303
+ this._heightMap.size === 0) {
8642
9304
  return;
8643
9305
  }
8644
9306
  let changed = false;
@@ -8765,13 +9427,36 @@ class MozGridCellComponent {
8765
9427
  const editorEl = this.elRef.nativeElement.querySelector('.grid-cell__editor');
8766
9428
  if (!editorEl)
8767
9429
  return;
8768
- // Temporarily remove overflow constraint to measure natural content width
8769
9430
  const cell = this.elRef.nativeElement.querySelector('.grid-cell');
9431
+ // The cell-level `overflow: hidden`, the editor's `width: 100%`, the
9432
+ // `flex-wrap: wrap` on `.grid-cell__editor-custom` and the global
9433
+ // `.grid-cell__editor ::ng-deep * { max-width: 100% }` rule all collapse
9434
+ // the editor's content back inside the current column width. Lift each of
9435
+ // those — on the editor and every descendant — so scrollWidth reports
9436
+ // the true natural width. Restore everything after measuring.
9437
+ const prevCellOverflow = cell?.style.overflow ?? '';
9438
+ const prevEditorCss = editorEl.style.cssText;
9439
+ const descendants = editorEl.querySelectorAll('*');
9440
+ const prevDescendantCss = [];
9441
+ descendants.forEach((el) => {
9442
+ prevDescendantCss.push(el.style.cssText);
9443
+ el.style.maxWidth = 'none';
9444
+ el.style.minWidth = 'max-content';
9445
+ el.style.flexWrap = 'nowrap';
9446
+ el.style.overflow = 'visible';
9447
+ });
8770
9448
  if (cell)
8771
9449
  cell.style.overflow = 'visible';
9450
+ editorEl.style.width = 'max-content';
9451
+ editorEl.style.maxWidth = 'none';
9452
+ editorEl.style.overflow = 'visible';
8772
9453
  const contentWidth = editorEl.scrollWidth + 16; // 16 = cell horizontal padding (8px × 2)
8773
9454
  if (cell)
8774
- cell.style.overflow = '';
9455
+ cell.style.overflow = prevCellOverflow;
9456
+ editorEl.style.cssText = prevEditorCss;
9457
+ descendants.forEach((el, i) => {
9458
+ el.style.cssText = prevDescendantCss[i];
9459
+ });
8775
9460
  if (contentWidth > currentWidth) {
8776
9461
  this.state.updateColumnState(field, { currentWidth: contentWidth });
8777
9462
  }
@@ -8950,7 +9635,7 @@ class MozGridCellComponent {
8950
9635
  });
8951
9636
  }
8952
9637
  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 });
9638
+ 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
9639
  }
8955
9640
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridCellComponent, decorators: [{
8956
9641
  type: Component,
@@ -8966,7 +9651,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8966
9651
  '[style.flex]': 'isLast() ? "1 0 auto" : "0 0 auto"',
8967
9652
  '[style.width.px]': 'isLast() ? undefined : colState().currentWidth',
8968
9653
  '[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"] }]
9654
+ }, 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
9655
  }], 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
9656
 
8972
9657
  class MozGridRowComponent {
@@ -9541,6 +10226,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
9541
10226
  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
10227
  }] });
9543
10228
 
10229
+ class MozGridFilterDrawerComponent {
10230
+ drawerRef = inject(MozDrawerRef);
10231
+ data = inject(DRAWER_DATA);
10232
+ availableColumns = this.data.availableColumns;
10233
+ draft = signal({
10234
+ conditions: this.data.model.conditions.map((c) => ({ ...c, value: { ...c.value } })),
10235
+ }, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
10236
+ activeCount = computed(() => this.draft().conditions.length, ...(ngDevMode ? [{ debugName: "activeCount" }] : /* istanbul ignore next */ []));
10237
+ onDraftChange(model) {
10238
+ this.draft.set(model);
10239
+ }
10240
+ apply() {
10241
+ this.drawerRef.close({ model: this.draft(), applied: true });
10242
+ }
10243
+ clearAll() {
10244
+ this.draft.set({ conditions: [] });
10245
+ }
10246
+ cancel() {
10247
+ this.drawerRef.close({ model: this.data.model, applied: false });
10248
+ }
10249
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10250
+ 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 });
10251
+ }
10252
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridFilterDrawerComponent, decorators: [{
10253
+ type: Component,
10254
+ 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"] }]
10255
+ }] });
10256
+
9544
10257
  class MozGridComponent {
9545
10258
  state = inject(GridStateManager);
9546
10259
  gridEngine = inject(GridEngine);
@@ -9608,7 +10321,28 @@ class MozGridComponent {
9608
10321
  // --- Internal ---
9609
10322
  isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : /* istanbul ignore next */ []));
9610
10323
  groupPanelOpen = signal(false, ...(ngDevMode ? [{ debugName: "groupPanelOpen" }] : /* istanbul ignore next */ []));
9611
- filterPanelOpen = signal(false, ...(ngDevMode ? [{ debugName: "filterPanelOpen" }] : /* istanbul ignore next */ []));
10324
+ /**
10325
+ * Mode controlling how the builder emits `filterChange`:
10326
+ * - `auto` : each mutation triggers an event (default in `client` mode).
10327
+ * - `manual` : only an explicit Apply emits (default in `server` mode).
10328
+ */
10329
+ filterApplyMode = input(null, ...(ngDevMode ? [{ debugName: "filterApplyMode" }] : /* istanbul ignore next */ []));
10330
+ resolvedFilterApplyMode = computed(() => {
10331
+ const override = this.filterApplyMode();
10332
+ if (override)
10333
+ return override;
10334
+ return this.state.mode() === 'server' ? 'manual' : 'auto';
10335
+ }, ...(ngDevMode ? [{ debugName: "resolvedFilterApplyMode" }] : /* istanbul ignore next */ []));
10336
+ /** Display descriptors for the "FILTERED BY" tag bar. */
10337
+ activeFilters = computed(() => {
10338
+ return this.filterEngine.conditions().map((c) => ({
10339
+ id: c.id,
10340
+ field: c.field,
10341
+ label: this.filterEngine.toLabel(c),
10342
+ removable: true,
10343
+ }));
10344
+ }, ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
10345
+ activeFilterCount = computed(() => this.filterEngine.conditions().length, ...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
9612
10346
  hasHiddenColumns = computed(() => this.state.columnStates().some((col) => !col.visible), ...(ngDevMode ? [{ debugName: "hasHiddenColumns" }] : /* istanbul ignore next */ []));
9613
10347
  hiddenColumnsList = computed(() => {
9614
10348
  const defMap = this.state.columnDefMap();
@@ -9799,7 +10533,7 @@ class MozGridComponent {
9799
10533
  this.persistenceEngine.restore(key);
9800
10534
  }
9801
10535
  }, { allowSignalWrites: true });
9802
- // Auto-save state on column/sort changes
10536
+ // Auto-save state on column/sort/filter changes
9803
10537
  effect(() => {
9804
10538
  const key = this.stateKey();
9805
10539
  if (!key)
@@ -9807,8 +10541,19 @@ class MozGridComponent {
9807
10541
  // Read signals to track them
9808
10542
  this.state.columnStates();
9809
10543
  this.state.activeSorts();
10544
+ this.state.filterModel();
9810
10545
  this.persistenceEngine.save(key);
9811
10546
  });
10547
+ // Emit `filterChange` once per mutation. The engine's `lastEvent` signal
10548
+ // is set synchronously from every mutation path, guaranteeing we emit
10549
+ // exactly once per reason (add / update / remove / reorder / clear / replace).
10550
+ effect(() => {
10551
+ const event = this.filterEngine.lastEvent();
10552
+ if (!event)
10553
+ return;
10554
+ this.filterChange.emit(event);
10555
+ this.resetInfiniteScrollIfActive();
10556
+ });
9812
10557
  // Initialize plugins
9813
10558
  effect(() => {
9814
10559
  const pluginList = this.plugins();
@@ -10306,17 +11051,27 @@ class MozGridComponent {
10306
11051
  this.state.activeSelectionMode.set('none');
10307
11052
  this.selectionChange.emit(this.rowSelectionEngine.getSelectionEvent());
10308
11053
  }
10309
- onRemoveFilter(field) {
10310
- this.filterEngine.removeFilter(field);
10311
- this.filterChange.emit({ filters: this.state.activeFilters() });
10312
- this.resetInfiniteScrollIfActive();
11054
+ /** Removes a single condition by id (tag "×" button). */
11055
+ onRemoveFilter(id) {
11056
+ this.filterEngine.removeCondition(id);
10313
11057
  }
11058
+ /** Clears the whole filter model ("Remove all" button). */
10314
11059
  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();
11060
+ this.filterEngine.clearAll();
11061
+ }
11062
+ // --- Filter drawer ---
11063
+ onFiltersClick() {
11064
+ const data = {
11065
+ model: { conditions: this.filterEngine.conditions().slice() },
11066
+ availableColumns: this.filterEngine.describeFilterableColumns(),
11067
+ applyMode: this.resolvedFilterApplyMode(),
11068
+ };
11069
+ const ref = this.drawerService.open(MozGridFilterDrawerComponent, { title: 'Filters', data, extended: true });
11070
+ ref.afterClosed().subscribe((result) => {
11071
+ if (result?.applied) {
11072
+ this.filterEngine.setModel(result.model, 'replace');
11073
+ }
11074
+ });
10320
11075
  }
10321
11076
  onRestoreColumn(field) {
10322
11077
  this.state.updateColumnState(field, { visible: true });
@@ -10535,21 +11290,37 @@ class MozGridComponent {
10535
11290
  return;
10536
11291
  try {
10537
11292
  const text = await navigator.clipboard.readText();
10538
- const rows = text.split('\n').map((line) => line.split('\t'));
11293
+ const rows = text
11294
+ .replace(/\r?\n$/, '')
11295
+ .split('\n')
11296
+ .map((line) => line.split('\t'));
10539
11297
  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
- };
11298
+ // Excel-style: a single clipboard value pasted over a multi-cell selection
11299
+ // fills the entire selection rather than only the focused anchor.
11300
+ const isSingleValue = rows.length === 1 && rows[0].length === 1;
11301
+ const selection = this.cellSelectionEngine.getNormalizedRange();
11302
+ const isMultiCellSelection = !!selection &&
11303
+ (selection.start.row !== selection.end.row || selection.start.col !== selection.end.col);
11304
+ let pasteRange;
11305
+ let pasteChanges;
11306
+ if (isSingleValue && isMultiCellSelection && selection) {
11307
+ pasteRange = selection;
11308
+ pasteChanges = this.clipboardEngine.fillSelection(selection, rows[0][0]);
11309
+ }
11310
+ else {
11311
+ pasteRange = {
11312
+ start: { row: focused.row, col: focused.col },
11313
+ end: {
11314
+ row: Math.min(focused.row + rows.length - 1, this.state.sourceData().length - 1),
11315
+ col: Math.min(focused.col + (rows[0]?.length ?? 1) - 1, cols.length - 1),
11316
+ },
11317
+ };
11318
+ pasteChanges = this.clipboardEngine.applyPaste(pasteRange, rows);
11319
+ }
10548
11320
  // If a cut is pending, first wipe the source cells so cut+paste == move,
10549
11321
  // and fold both halves into a single undoable history op.
10550
11322
  const cutSource = this.state.cutSource();
10551
11323
  const clearChanges = cutSource ? this.clipboardEngine.clearRange(cutSource) : [];
10552
- const pasteChanges = this.clipboardEngine.applyPaste(pasteRange, rows);
10553
11324
  const allChanges = [...clearChanges, ...pasteChanges];
10554
11325
  if (allChanges.length > 0) {
10555
11326
  this.historyEngine.record(cutSource ? 'cut' : 'paste', allChanges);
@@ -10800,7 +11571,7 @@ class MozGridComponent {
10800
11571
  return value;
10801
11572
  }
10802
11573
  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: [
11574
+ 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 }, 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
11575
  GridStateManager,
10805
11576
  GridEngine,
10806
11577
  SortEngine,
@@ -10854,15 +11625,24 @@ class MozGridComponent {
10854
11625
  <Download20 icon />
10855
11626
  </moz-icon-button>
10856
11627
  }
10857
- <moz-icon-button
11628
+ <button
11629
+ type="button"
11630
+ moz-button
10858
11631
  id="grid-filter"
10859
11632
  size="s"
10860
11633
  [ghost]="true"
10861
- ariaLabel="Filter"
10862
- (activated)="filterPanelOpen.set(!filterPanelOpen())"
11634
+ [ariaLabel]="'Filters'"
11635
+ class="moz-grid__toolbar-filter-btn"
11636
+ (click)="onFiltersClick()"
10863
11637
  >
10864
11638
  <Filter20 icon />
10865
- </moz-icon-button>
11639
+ <span>Filters</span>
11640
+ @if (activeFilterCount() > 0) {
11641
+ <span class="moz-grid__toolbar-filter-badge" aria-hidden="true">
11642
+ {{ activeFilterCount() }}
11643
+ </span>
11644
+ }
11645
+ </button>
10866
11646
  <moz-icon-button
10867
11647
  id="grid-settings"
10868
11648
  size="s"
@@ -10972,15 +11752,15 @@ class MozGridComponent {
10972
11752
  </button>
10973
11753
  }
10974
11754
  </div>
10975
- } @if (state.activeFilters().length > 0) {
11755
+ } @if (activeFilters().length > 0) {
10976
11756
  <div class="moz-grid__tag-bar">
10977
11757
  <span class="moz-grid__tag-bar-label">FILTERED BY</span>
10978
- @for (filter of state.activeFilters(); track filter.field) {
11758
+ @for (filter of activeFilters(); track filter.id) {
10979
11759
  <moz-tag
10980
11760
  [type]="filter.removable ? 'removable' : 'informative'"
10981
11761
  size="s"
10982
- [id]="'filter-' + filter.field"
10983
- (removeTag)="onRemoveFilter(filter.field)"
11762
+ [id]="'filter-' + filter.id"
11763
+ (removeTag)="onRemoveFilter(filter.id)"
10984
11764
  >{{ filter.label }}</moz-tag
10985
11765
  >
10986
11766
  }
@@ -11042,7 +11822,7 @@ class MozGridComponent {
11042
11822
  />
11043
11823
  </div>
11044
11824
  </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 });
11825
+ `, 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__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: 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
11826
  }
11047
11827
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridComponent, decorators: [{
11048
11828
  type: Component,
@@ -11119,15 +11899,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11119
11899
  <Download20 icon />
11120
11900
  </moz-icon-button>
11121
11901
  }
11122
- <moz-icon-button
11902
+ <button
11903
+ type="button"
11904
+ moz-button
11123
11905
  id="grid-filter"
11124
11906
  size="s"
11125
11907
  [ghost]="true"
11126
- ariaLabel="Filter"
11127
- (activated)="filterPanelOpen.set(!filterPanelOpen())"
11908
+ [ariaLabel]="'Filters'"
11909
+ class="moz-grid__toolbar-filter-btn"
11910
+ (click)="onFiltersClick()"
11128
11911
  >
11129
11912
  <Filter20 icon />
11130
- </moz-icon-button>
11913
+ <span>Filters</span>
11914
+ @if (activeFilterCount() > 0) {
11915
+ <span class="moz-grid__toolbar-filter-badge" aria-hidden="true">
11916
+ {{ activeFilterCount() }}
11917
+ </span>
11918
+ }
11919
+ </button>
11131
11920
  <moz-icon-button
11132
11921
  id="grid-settings"
11133
11922
  size="s"
@@ -11237,15 +12026,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11237
12026
  </button>
11238
12027
  }
11239
12028
  </div>
11240
- } @if (state.activeFilters().length > 0) {
12029
+ } @if (activeFilters().length > 0) {
11241
12030
  <div class="moz-grid__tag-bar">
11242
12031
  <span class="moz-grid__tag-bar-label">FILTERED BY</span>
11243
- @for (filter of state.activeFilters(); track filter.field) {
12032
+ @for (filter of activeFilters(); track filter.id) {
11244
12033
  <moz-tag
11245
12034
  [type]="filter.removable ? 'removable' : 'informative'"
11246
12035
  size="s"
11247
- [id]="'filter-' + filter.field"
11248
- (removeTag)="onRemoveFilter(filter.field)"
12036
+ [id]="'filter-' + filter.id"
12037
+ (removeTag)="onRemoveFilter(filter.id)"
11249
12038
  >{{ filter.label }}</moz-tag
11250
12039
  >
11251
12040
  }
@@ -11307,8 +12096,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11307
12096
  />
11308
12097
  </div>
11309
12098
  </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"] }] } });
12099
+ `, 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__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"] }]
12100
+ }], 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"] }], filterApplyMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterApplyMode", required: false }] }] } });
11312
12101
 
11313
12102
  const DEFAULT_GRID_OPTIONS = {
11314
12103
  mode: 'client',