@mozaic-ds/angular 2.0.43 → 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}`;
5251
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}`;
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);
@@ -5458,6 +5806,19 @@ class GridEngine {
5458
5806
  return this.state.totalItems();
5459
5807
  return this.filteredData().length;
5460
5808
  }, ...(ngDevMode ? [{ debugName: "computedTotalItems" }] : /* istanbul ignore next */ []));
5809
+ /**
5810
+ * Resolves a display row index (as emitted via `DisplayRow.index`) to the
5811
+ * actual index in `sourceData()`. When sort/filter/group is active these do
5812
+ * not match, so we look up the display row's data object and search for it
5813
+ * in sourceData. Returns -1 when the display index is unknown.
5814
+ */
5815
+ displayIndexToSourceIndex(displayIndex) {
5816
+ const rows = this.displayRows();
5817
+ const row = rows.find((r) => r.type === 'data' && r.index === displayIndex);
5818
+ if (!row || row.type !== 'data')
5819
+ return -1;
5820
+ return this.state.sourceData().indexOf(row.data);
5821
+ }
5461
5822
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GridEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5462
5823
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GridEngine });
5463
5824
  }
@@ -5527,7 +5888,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
5527
5888
  type: Injectable
5528
5889
  }] });
5529
5890
 
5530
- const PASTE_SKIP$1 = Symbol('PASTE_SKIP');
5891
+ const PASTE_SKIP = Symbol('PASTE_SKIP');
5531
5892
  /**
5532
5893
  * Applies a set of cell-level mutations to sourceData and returns the list of
5533
5894
  * actual changes that occurred, so the caller (usually the history engine) can
@@ -5535,8 +5896,23 @@ const PASTE_SKIP$1 = Symbol('PASTE_SKIP');
5535
5896
  */
5536
5897
  class ClipboardEngine {
5537
5898
  state = inject(GridStateManager);
5899
+ gridEngine = inject(GridEngine);
5538
5900
  /** Derived by components (marching-ants outline). */
5539
5901
  cutRange = computed(() => this.state.cutSource(), ...(ngDevMode ? [{ debugName: "cutRange" }] : /* istanbul ignore next */ []));
5902
+ /**
5903
+ * Maps every display row index inside `range` to its actual sourceData
5904
+ * index. Unknown indices (e.g. outside the current page) are skipped so
5905
+ * callers can safely iterate only over rows that exist in sourceData.
5906
+ */
5907
+ resolveRangeSourceIndices(range) {
5908
+ const out = [];
5909
+ for (let r = range.start.row; r <= range.end.row; r++) {
5910
+ const srcIdx = this.gridEngine.displayIndexToSourceIndex(r);
5911
+ if (srcIdx >= 0)
5912
+ out.push(srcIdx);
5913
+ }
5914
+ return out;
5915
+ }
5540
5916
  markCut(range) {
5541
5917
  this.state.cutSource.set(range);
5542
5918
  }
@@ -5550,15 +5926,19 @@ class ClipboardEngine {
5550
5926
  const cols = this.state.visibleColumns();
5551
5927
  const defMap = this.state.columnDefMap();
5552
5928
  const changes = [];
5929
+ const sourceIndices = this.resolveRangeSourceIndices(range);
5930
+ if (sourceIndices.length < 2)
5931
+ return [];
5932
+ const [sourceIdx, ...targetIndices] = sourceIndices;
5553
5933
  this.state.sourceData.update((data) => {
5554
5934
  const updated = [...data];
5555
- const sourceRow = updated[range.start.row];
5935
+ const sourceRow = updated[sourceIdx];
5556
5936
  if (!sourceRow)
5557
5937
  return updated;
5558
- for (let r = range.start.row + 1; r <= range.end.row; r++) {
5559
- if (!updated[r])
5938
+ for (const targetIdx of targetIndices) {
5939
+ if (!updated[targetIdx])
5560
5940
  continue;
5561
- const rowCopy = { ...updated[r] };
5941
+ const rowCopy = { ...updated[targetIdx] };
5562
5942
  let changed = false;
5563
5943
  for (let c = range.start.col; c <= range.end.col; c++) {
5564
5944
  const field = cols[c]?.field;
@@ -5570,18 +5950,18 @@ class ClipboardEngine {
5570
5950
  const sourceValue = def.valueGetter
5571
5951
  ? def.valueGetter(sourceRow)
5572
5952
  : sourceRow[field];
5573
- const coerced = this.coerceAndValidate(field, sourceValue, updated[r]);
5574
- if (coerced === PASTE_SKIP$1)
5953
+ const coerced = this.coerceAndValidate(field, sourceValue, updated[targetIdx]);
5954
+ if (coerced === PASTE_SKIP)
5575
5955
  continue;
5576
- const before = updated[r][field];
5956
+ const before = updated[targetIdx][field];
5577
5957
  if (before === coerced)
5578
5958
  continue;
5579
5959
  rowCopy[field] = coerced;
5580
- changes.push({ rowIndex: r, field, before, after: coerced });
5960
+ changes.push({ rowIndex: targetIdx, field, before, after: coerced });
5581
5961
  changed = true;
5582
5962
  }
5583
5963
  if (changed)
5584
- updated[r] = rowCopy;
5964
+ updated[targetIdx] = rowCopy;
5585
5965
  }
5586
5966
  return updated;
5587
5967
  });
@@ -5600,10 +5980,11 @@ class ClipboardEngine {
5600
5980
  if (!sourceDef)
5601
5981
  return [];
5602
5982
  const changes = [];
5983
+ const sourceIndices = this.resolveRangeSourceIndices(range);
5603
5984
  this.state.sourceData.update((data) => {
5604
5985
  const updated = [...data];
5605
- for (let r = range.start.row; r <= range.end.row; r++) {
5606
- const row = updated[r];
5986
+ for (const sourceIdx of sourceIndices) {
5987
+ const row = updated[sourceIdx];
5607
5988
  if (!row)
5608
5989
  continue;
5609
5990
  const sourceValue = sourceDef.valueGetter
@@ -5619,17 +6000,17 @@ class ClipboardEngine {
5619
6000
  if (!def?.editable)
5620
6001
  continue;
5621
6002
  const coerced = this.coerceAndValidate(field, sourceValue, row);
5622
- if (coerced === PASTE_SKIP$1)
6003
+ if (coerced === PASTE_SKIP)
5623
6004
  continue;
5624
6005
  const before = row[field];
5625
6006
  if (before === coerced)
5626
6007
  continue;
5627
6008
  rowCopy[field] = coerced;
5628
- changes.push({ rowIndex: r, field, before, after: coerced });
6009
+ changes.push({ rowIndex: sourceIdx, field, before, after: coerced });
5629
6010
  changed = true;
5630
6011
  }
5631
6012
  if (changed)
5632
- updated[r] = rowCopy;
6013
+ updated[sourceIdx] = rowCopy;
5633
6014
  }
5634
6015
  return updated;
5635
6016
  });
@@ -5640,10 +6021,11 @@ class ClipboardEngine {
5640
6021
  const cols = this.state.visibleColumns();
5641
6022
  const defMap = this.state.columnDefMap();
5642
6023
  const changes = [];
6024
+ const sourceIndices = this.resolveRangeSourceIndices(range);
5643
6025
  this.state.sourceData.update((data) => {
5644
6026
  const updated = [...data];
5645
- for (let r = range.start.row; r <= range.end.row; r++) {
5646
- const row = updated[r];
6027
+ for (const sourceIdx of sourceIndices) {
6028
+ const row = updated[sourceIdx];
5647
6029
  if (!row)
5648
6030
  continue;
5649
6031
  const rowCopy = { ...row };
@@ -5656,17 +6038,17 @@ class ClipboardEngine {
5656
6038
  if (!def?.editable)
5657
6039
  continue;
5658
6040
  const coerced = this.coerceAndValidate(field, value, row);
5659
- if (coerced === PASTE_SKIP$1)
6041
+ if (coerced === PASTE_SKIP)
5660
6042
  continue;
5661
6043
  const before = row[field];
5662
6044
  if (before === coerced)
5663
6045
  continue;
5664
6046
  rowCopy[field] = coerced;
5665
- changes.push({ rowIndex: r, field, before, after: coerced });
6047
+ changes.push({ rowIndex: sourceIdx, field, before, after: coerced });
5666
6048
  changed = true;
5667
6049
  }
5668
6050
  if (changed)
5669
- updated[r] = rowCopy;
6051
+ updated[sourceIdx] = rowCopy;
5670
6052
  }
5671
6053
  return updated;
5672
6054
  });
@@ -5677,10 +6059,11 @@ class ClipboardEngine {
5677
6059
  const cols = this.state.visibleColumns();
5678
6060
  const defMap = this.state.columnDefMap();
5679
6061
  const changes = [];
6062
+ const sourceIndices = this.resolveRangeSourceIndices(range);
5680
6063
  this.state.sourceData.update((data) => {
5681
6064
  const updated = [...data];
5682
- for (let r = range.start.row; r <= range.end.row; r++) {
5683
- const row = updated[r];
6065
+ for (const sourceIdx of sourceIndices) {
6066
+ const row = updated[sourceIdx];
5684
6067
  if (!row)
5685
6068
  continue;
5686
6069
  const rowCopy = { ...row };
@@ -5693,17 +6076,17 @@ class ClipboardEngine {
5693
6076
  if (!def?.editable)
5694
6077
  continue;
5695
6078
  const coerced = this.coerceAndValidate(field, null, row);
5696
- if (coerced === PASTE_SKIP$1)
6079
+ if (coerced === PASTE_SKIP)
5697
6080
  continue;
5698
6081
  const before = row[field];
5699
6082
  if (before === coerced)
5700
6083
  continue;
5701
6084
  rowCopy[field] = coerced;
5702
- changes.push({ rowIndex: r, field, before, after: coerced });
6085
+ changes.push({ rowIndex: sourceIdx, field, before, after: coerced });
5703
6086
  changed = true;
5704
6087
  }
5705
6088
  if (changed)
5706
- updated[r] = rowCopy;
6089
+ updated[sourceIdx] = rowCopy;
5707
6090
  }
5708
6091
  return updated;
5709
6092
  });
@@ -5716,10 +6099,11 @@ class ClipboardEngine {
5716
6099
  this.state.sourceData.update((data) => {
5717
6100
  const updated = [...data];
5718
6101
  for (let ri = 0; ri < pasteRows.length; ri++) {
5719
- const targetRow = range.start.row + ri;
5720
- if (targetRow >= updated.length)
5721
- break;
5722
- const row = updated[targetRow];
6102
+ const targetDisplayRow = range.start.row + ri;
6103
+ const sourceIdx = this.gridEngine.displayIndexToSourceIndex(targetDisplayRow);
6104
+ if (sourceIdx < 0)
6105
+ continue;
6106
+ const row = updated[sourceIdx];
5723
6107
  if (!row)
5724
6108
  continue;
5725
6109
  const rowCopy = { ...row };
@@ -5732,17 +6116,17 @@ class ClipboardEngine {
5732
6116
  if (!field)
5733
6117
  continue;
5734
6118
  const coerced = this.coerceAndValidate(field, pasteRows[ri][ci], row);
5735
- if (coerced === PASTE_SKIP$1)
6119
+ if (coerced === PASTE_SKIP)
5736
6120
  continue;
5737
6121
  const before = row[field];
5738
6122
  if (before === coerced)
5739
6123
  continue;
5740
6124
  rowCopy[field] = coerced;
5741
- changes.push({ rowIndex: targetRow, field, before, after: coerced });
6125
+ changes.push({ rowIndex: sourceIdx, field, before, after: coerced });
5742
6126
  changed = true;
5743
6127
  }
5744
6128
  if (changed)
5745
- updated[targetRow] = rowCopy;
6129
+ updated[sourceIdx] = rowCopy;
5746
6130
  }
5747
6131
  return updated;
5748
6132
  });
@@ -5785,7 +6169,7 @@ class ClipboardEngine {
5785
6169
  coerceAndValidate(field, rawValue, row) {
5786
6170
  const def = this.state.columnDefMap().get(field);
5787
6171
  if (!def?.editable)
5788
- return PASTE_SKIP$1;
6172
+ return PASTE_SKIP;
5789
6173
  const editorType = def.cellEditor;
5790
6174
  if (rawValue === null) {
5791
6175
  let clearValue;
@@ -5802,7 +6186,7 @@ class ClipboardEngine {
5802
6186
  if (def.cellEditorValidator) {
5803
6187
  const result = def.cellEditorValidator(clearValue, row);
5804
6188
  if (result === false || typeof result === 'string')
5805
- return PASTE_SKIP$1;
6189
+ return PASTE_SKIP;
5806
6190
  }
5807
6191
  return clearValue;
5808
6192
  }
@@ -5810,7 +6194,7 @@ class ClipboardEngine {
5810
6194
  if (editorType === 'number') {
5811
6195
  const num = Number(rawValue);
5812
6196
  if (isNaN(num))
5813
- return PASTE_SKIP$1;
6197
+ return PASTE_SKIP;
5814
6198
  value = num;
5815
6199
  }
5816
6200
  else if (editorType === 'checkbox') {
@@ -5821,19 +6205,19 @@ class ClipboardEngine {
5821
6205
  value = false;
5822
6206
  }
5823
6207
  else {
5824
- return PASTE_SKIP$1;
6208
+ return PASTE_SKIP;
5825
6209
  }
5826
6210
  }
5827
6211
  else if (editorType === 'select' && def.cellEditorOptions?.length) {
5828
6212
  const allowed = def.cellEditorOptions.map((o) => String(o.value));
5829
6213
  if (!allowed.includes(String(rawValue)))
5830
- return PASTE_SKIP$1;
6214
+ return PASTE_SKIP;
5831
6215
  value = rawValue;
5832
6216
  }
5833
6217
  if (def.cellEditorValidator) {
5834
6218
  const result = def.cellEditorValidator(value, row);
5835
6219
  if (result === false || typeof result === 'string')
5836
- return PASTE_SKIP$1;
6220
+ return PASTE_SKIP;
5837
6221
  }
5838
6222
  return value;
5839
6223
  }
@@ -6006,6 +6390,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
6006
6390
  class InlineEditEngine {
6007
6391
  state = inject(GridStateManager);
6008
6392
  history = inject(HistoryEngine);
6393
+ gridEngine = inject(GridEngine);
6009
6394
  startEdit(rowIndex, field) {
6010
6395
  const defMap = this.state.columnDefMap();
6011
6396
  const def = defMap.get(field);
@@ -6014,7 +6399,8 @@ class InlineEditEngine {
6014
6399
  const colIndex = this.state.visibleColumns().findIndex((c) => c.field === field);
6015
6400
  if (colIndex < 0)
6016
6401
  return;
6017
- const row = this.state.sourceData()[rowIndex];
6402
+ const sourceIndex = this.gridEngine.displayIndexToSourceIndex(rowIndex);
6403
+ const row = sourceIndex >= 0 ? this.state.sourceData()[sourceIndex] : undefined;
6018
6404
  if (!row)
6019
6405
  return;
6020
6406
  const value = def.valueGetter ? def.valueGetter(row) : row[field];
@@ -6039,7 +6425,8 @@ class InlineEditEngine {
6039
6425
  const colIndex = this.state.visibleColumns().findIndex((c) => c.field === field);
6040
6426
  if (colIndex < 0)
6041
6427
  return;
6042
- const row = this.state.sourceData()[rowIndex];
6428
+ const sourceIndex = this.gridEngine.displayIndexToSourceIndex(rowIndex);
6429
+ const row = sourceIndex >= 0 ? this.state.sourceData()[sourceIndex] : undefined;
6043
6430
  if (!row)
6044
6431
  return;
6045
6432
  const currentValue = def.valueGetter
@@ -6086,7 +6473,10 @@ class InlineEditEngine {
6086
6473
  if (!field)
6087
6474
  return null;
6088
6475
  const def = this.state.columnDefMap().get(field);
6089
- let row = this.state.sourceData()[rowIndex];
6476
+ const sourceIndex = this.gridEngine.displayIndexToSourceIndex(rowIndex);
6477
+ if (sourceIndex < 0)
6478
+ return null;
6479
+ let row = this.state.sourceData()[sourceIndex];
6090
6480
  if (!row)
6091
6481
  return null;
6092
6482
  // Validation
@@ -6107,26 +6497,25 @@ class InlineEditEngine {
6107
6497
  return null;
6108
6498
  }
6109
6499
  }
6110
- // In client mode, update data immutably so signals detect the change
6111
- if (this.state.mode() === 'client') {
6112
- this.state.sourceData.update((data) => {
6113
- const updated = [...data];
6114
- updated[rowIndex] = { ...updated[rowIndex], [field]: editState.draftValue };
6115
- return updated;
6116
- });
6117
- // Re-read the updated row for the event
6118
- row = this.state.sourceData()[rowIndex];
6119
- }
6500
+ // Always update data immutably so signals detect the change, regardless
6501
+ // of client/server mode. Consumers can still re-fetch/override via output.
6502
+ this.state.sourceData.update((data) => {
6503
+ const updated = [...data];
6504
+ updated[sourceIndex] = { ...updated[sourceIndex], [field]: editState.draftValue };
6505
+ return updated;
6506
+ });
6507
+ // Re-read the updated row for the event
6508
+ row = this.state.sourceData()[sourceIndex];
6120
6509
  const event = {
6121
6510
  row,
6122
- rowIndex,
6511
+ rowIndex: sourceIndex,
6123
6512
  field,
6124
6513
  oldValue: editState.originalValue,
6125
6514
  newValue: editState.draftValue,
6126
6515
  };
6127
- if (this.state.mode() === 'client' && event.oldValue !== event.newValue) {
6516
+ if (event.oldValue !== event.newValue) {
6128
6517
  this.history.record('edit', [
6129
- { rowIndex, field, before: event.oldValue, after: event.newValue },
6518
+ { rowIndex: sourceIndex, field, before: event.oldValue, after: event.newValue },
6130
6519
  ]);
6131
6520
  }
6132
6521
  this.state.cellEditState.set({
@@ -6182,7 +6571,13 @@ class RowSelectionEngine {
6182
6571
  engine = inject(GridEngine);
6183
6572
  selectedIds = computed(() => this.state.selectedRowIds(), ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
6184
6573
  excludedIds = computed(() => this.state.excludedRowIds(), ...(ngDevMode ? [{ debugName: "excludedIds" }] : /* istanbul ignore next */ []));
6185
- lastToggledIndex = signal(-1, ...(ngDevMode ? [{ debugName: "lastToggledIndex" }] : /* istanbul ignore next */ []));
6574
+ /**
6575
+ * Anchor row for shift-click range selection. We track the row object
6576
+ * (not its index) so the anchor stays valid across grouping, sorting and
6577
+ * filtering — where display indices shift without the underlying row
6578
+ * changing.
6579
+ */
6580
+ lastToggledRow = signal(null, ...(ngDevMode ? [{ debugName: "lastToggledRow" }] : /* istanbul ignore next */ []));
6186
6581
  count = computed(() => {
6187
6582
  const mode = this.state.selectAllMode();
6188
6583
  if (mode === 'all') {
@@ -6208,23 +6603,41 @@ class RowSelectionEngine {
6208
6603
  const pageSelected = pageData.filter((row) => this.isRowSelected(row)).length;
6209
6604
  return pageSelected > 0 && pageSelected < pageData.length;
6210
6605
  }, ...(ngDevMode ? [{ debugName: "isIndeterminate" }] : /* istanbul ignore next */ []));
6211
- selectRowRange(fromIndex, toIndex) {
6606
+ /**
6607
+ * Extends the selection from the last-toggled row (the anchor) to `endRow`,
6608
+ * resolving positions by object identity against the currently paginated
6609
+ * data. This is robust to grouping / sorting / filtering because we don't
6610
+ * rely on numeric indices, and works across any visible page slice.
6611
+ */
6612
+ selectRowRangeToRow(endRow) {
6613
+ const anchor = this.lastToggledRow();
6614
+ if (!anchor) {
6615
+ this.toggleRow(endRow);
6616
+ this.lastToggledRow.set(endRow);
6617
+ return;
6618
+ }
6212
6619
  const pageData = this.engine.paginatedData();
6213
- const start = Math.min(fromIndex, toIndex);
6214
- const end = Math.max(fromIndex, toIndex);
6620
+ const anchorIdx = pageData.indexOf(anchor);
6621
+ const endIdx = pageData.indexOf(endRow);
6622
+ if (anchorIdx < 0 || endIdx < 0) {
6623
+ // Anchor left the visible page — fall back to a plain toggle
6624
+ this.toggleRow(endRow);
6625
+ this.lastToggledRow.set(endRow);
6626
+ return;
6627
+ }
6628
+ const start = Math.min(anchorIdx, endIdx);
6629
+ const end = Math.max(anchorIdx, endIdx);
6215
6630
  this.state.selectedRowIds.update((ids) => {
6216
6631
  const next = new Set(ids);
6217
6632
  for (let i = start; i <= end; i++) {
6218
- if (i >= 0 && i < pageData.length) {
6219
- next.add(this.getRowId(pageData[i]));
6220
- }
6633
+ next.add(this.getRowId(pageData[i]));
6221
6634
  }
6222
6635
  return next;
6223
6636
  });
6224
6637
  if (this.state.selectAllMode() === 'none') {
6225
6638
  this.state.selectAllMode.set('page');
6226
6639
  }
6227
- this.lastToggledIndex.set(toIndex);
6640
+ this.lastToggledRow.set(endRow);
6228
6641
  }
6229
6642
  toggleRow(row) {
6230
6643
  const id = this.getRowId(row);
@@ -7672,6 +8085,7 @@ class StatePersistenceEngine {
7672
8085
  pinned: col.pinned,
7673
8086
  })),
7674
8087
  sorts: this.state.activeSorts(),
8088
+ filters: this.state.filterModel().conditions.map(({ id: _id, ...rest }) => rest),
7675
8089
  };
7676
8090
  try {
7677
8091
  localStorage.setItem(storageKey, JSON.stringify(persisted));
@@ -7715,6 +8129,11 @@ class StatePersistenceEngine {
7715
8129
  : { ...s, sort: null, sortIndex: null };
7716
8130
  }));
7717
8131
  }
8132
+ if (persisted.filters?.length) {
8133
+ this.state.filterModel.set({
8134
+ conditions: persisted.filters.map((f) => ({ ...f, id: generateConditionId() })),
8135
+ });
8136
+ }
7718
8137
  return true;
7719
8138
  }
7720
8139
  catch {
@@ -8082,13 +8501,272 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8082
8501
  }]
8083
8502
  }], propDecorators: { slot: [{ type: i0.Input, args: [{ isSignal: true, alias: "mozGridToolbarDef", required: false }] }] } });
8084
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
+
8085
8750
  class MozGridHeaderCellComponent {
8086
8751
  state = inject(GridStateManager);
8752
+ filterEngine = inject(FilterEngine);
8087
8753
  columnState = input.required(...(ngDevMode ? [{ debugName: "columnState" }] : /* istanbul ignore next */ []));
8088
8754
  def = input.required(...(ngDevMode ? [{ debugName: "def" }] : /* istanbul ignore next */ []));
8089
8755
  isLast = input(false, ...(ngDevMode ? [{ debugName: "isLast" }] : /* istanbul ignore next */ []));
8090
8756
  pinnedEnd = input(false, ...(ngDevMode ? [{ debugName: "pinnedEnd" }] : /* istanbul ignore next */ []));
8091
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 */ []));
8092
8770
  resolvedMinWidth = computed(() => {
8093
8771
  const def = this.def();
8094
8772
  return def.minWidth ? parseInt(def.minWidth, 10) || 50 : 50;
@@ -8114,6 +8792,7 @@ class MozGridHeaderCellComponent {
8114
8792
  items.push({
8115
8793
  id: 'filter-column',
8116
8794
  label: 'Filter in this column',
8795
+ icon: Filter20,
8117
8796
  divider: items.length > 0,
8118
8797
  });
8119
8798
  }
@@ -8151,11 +8830,6 @@ class MozGridHeaderCellComponent {
8151
8830
  divider: items.length > 0,
8152
8831
  });
8153
8832
  }
8154
- items.push({
8155
- id: 'toggle-column-search',
8156
- label: colState.searchVisible ? 'Hide search by column' : 'Show search by column',
8157
- divider: def.hideable === false && items.length > 0,
8158
- });
8159
8833
  return items;
8160
8834
  }, ...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
8161
8835
  onHeaderClick(event) {
@@ -8167,24 +8841,58 @@ class MozGridHeaderCellComponent {
8167
8841
  }
8168
8842
  }
8169
8843
  onMenuItemClick(item) {
8170
- this.menuAction.emit({
8171
- field: this.columnState().field,
8172
- actionId: item.id,
8173
- });
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);
8174
8875
  }
8175
8876
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridHeaderCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8176
- 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 });
8177
8878
  }
8178
8879
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridHeaderCellComponent, decorators: [{
8179
8880
  type: Component,
8180
- 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: {
8181
8889
  '[style.flex]': 'isLast() ? "1 0 auto" : "0 0 auto"',
8182
8890
  '[style.width.px]': 'isLast() ? undefined : columnState().currentWidth',
8183
8891
  '[style.min-width.px]': 'isLast() ? columnState().currentWidth : resolvedMinWidth()',
8184
8892
  '[class.grid-header-cell-host--dragging]': 'isDragging()',
8185
8893
  '[class.grid-header-cell-host--reorderable]': 'reorderable()',
8186
- }, 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"] }]
8187
- }], 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"] }] } });
8188
8896
 
8189
8897
  function trackDisplayRow(_index, row) {
8190
8898
  if (row.type === 'group') {
@@ -8437,6 +9145,12 @@ class AutoSizeVirtualScrollStrategy {
8437
9145
  const viewportSize = this._viewport.getViewportSize();
8438
9146
  const dataLength = this._viewport.getDataLength();
8439
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 };
8440
9154
  this._viewport.setRenderedRange({ start: 0, end: 0 });
8441
9155
  this._viewport.setRenderedContentOffset(0);
8442
9156
  return;
@@ -8584,8 +9298,9 @@ class AutoSizeVirtualScrollStrategy {
8584
9298
  // Quick check: if the first child matches the default height,
8585
9299
  // assume all rows are uniform and skip the full measurement pass.
8586
9300
  const firstHeight = children[0].offsetHeight;
8587
- if (firstHeight > 0 && Math.abs(firstHeight - this._defaultItemSize) <= 0.5
8588
- && this._heightMap.size === 0) {
9301
+ if (firstHeight > 0 &&
9302
+ Math.abs(firstHeight - this._defaultItemSize) <= 0.5 &&
9303
+ this._heightMap.size === 0) {
8589
9304
  return;
8590
9305
  }
8591
9306
  let changed = false;
@@ -8669,6 +9384,7 @@ class MozGridCellComponent {
8669
9384
  cellSelectionEngine = inject(CellSelectionEngine);
8670
9385
  validationEngine = inject(CellValidationEngine);
8671
9386
  clipboard = inject(ClipboardEngine);
9387
+ gridEngine = inject(GridEngine);
8672
9388
  elRef = inject((ElementRef));
8673
9389
  preEditWidth = null;
8674
9390
  constructor() {
@@ -8711,13 +9427,36 @@ class MozGridCellComponent {
8711
9427
  const editorEl = this.elRef.nativeElement.querySelector('.grid-cell__editor');
8712
9428
  if (!editorEl)
8713
9429
  return;
8714
- // Temporarily remove overflow constraint to measure natural content width
8715
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
+ });
8716
9448
  if (cell)
8717
9449
  cell.style.overflow = 'visible';
9450
+ editorEl.style.width = 'max-content';
9451
+ editorEl.style.maxWidth = 'none';
9452
+ editorEl.style.overflow = 'visible';
8718
9453
  const contentWidth = editorEl.scrollWidth + 16; // 16 = cell horizontal padding (8px × 2)
8719
9454
  if (cell)
8720
- 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
+ });
8721
9460
  if (contentWidth > currentWidth) {
8722
9461
  this.state.updateColumnState(field, { currentWidth: contentWidth });
8723
9462
  }
@@ -8784,7 +9523,15 @@ class MozGridCellComponent {
8784
9523
  return this.cellSelectionEngine.isCellInFillRejectRange(this.rowIndex(), this.colIndex());
8785
9524
  }, ...(ngDevMode ? [{ debugName: "isInFillRejectRange" }] : /* istanbul ignore next */ []));
8786
9525
  cutEdges = computed(() => this.clipboard.cutEdges(this.rowIndex(), this.colIndex()), ...(ngDevMode ? [{ debugName: "cutEdges" }] : /* istanbul ignore next */ []));
8787
- cellError = computed(() => this.validationEngine.getCellError(this.rowIndex(), this.def().field), ...(ngDevMode ? [{ debugName: "cellError" }] : /* istanbul ignore next */ []));
9526
+ cellError = computed(() => {
9527
+ // Validation errors are keyed by sourceData index, but `rowIndex()` is a
9528
+ // display/paginated index. Resolve to the source index so errors show on
9529
+ // the right cells when sort / filter / grouping is active.
9530
+ const sourceIndex = this.gridEngine.displayIndexToSourceIndex(this.rowIndex());
9531
+ if (sourceIndex < 0)
9532
+ return null;
9533
+ return this.validationEngine.getCellError(sourceIndex, this.def().field);
9534
+ }, ...(ngDevMode ? [{ debugName: "cellError" }] : /* istanbul ignore next */ []));
8788
9535
  onCellClick(event) {
8789
9536
  // Shift+click: extend range from current focused cell to this cell
8790
9537
  if (event.shiftKey) {
@@ -8888,7 +9635,7 @@ class MozGridCellComponent {
8888
9635
  });
8889
9636
  }
8890
9637
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8891
- 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:2px solid var(--Status-Border-Error, #ef5f5c);outline-offset:-2px;z-index:1}.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 });
8892
9639
  }
8893
9640
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridCellComponent, decorators: [{
8894
9641
  type: Component,
@@ -8904,7 +9651,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
8904
9651
  '[style.flex]': 'isLast() ? "1 0 auto" : "0 0 auto"',
8905
9652
  '[style.width.px]': 'isLast() ? undefined : colState().currentWidth',
8906
9653
  '[style.min-width.px]': 'isLast() ? colState().currentWidth : resolvedMinWidth()',
8907
- }, 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:2px solid var(--Status-Border-Error, #ef5f5c);outline-offset:-2px;z-index:1}.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"] }]
8908
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"] }] } });
8909
9656
 
8910
9657
  class MozGridRowComponent {
@@ -8944,12 +9691,12 @@ class MozGridRowComponent {
8944
9691
  }
8945
9692
  onCheckboxClick(event) {
8946
9693
  event.stopPropagation();
8947
- if (event.shiftKey && this.rowSelection.lastToggledIndex() >= 0) {
8948
- this.rowSelection.selectRowRange(this.rowSelection.lastToggledIndex(), this.rowIndex());
9694
+ if (event.shiftKey && this.rowSelection.lastToggledRow() !== null) {
9695
+ this.rowSelection.selectRowRangeToRow(this.row());
8949
9696
  }
8950
9697
  else {
8951
9698
  this.rowSelection.toggleRow(this.row());
8952
- this.rowSelection.lastToggledIndex.set(this.rowIndex());
9699
+ this.rowSelection.lastToggledRow.set(this.row());
8953
9700
  }
8954
9701
  this.rowSelectionToggle.emit();
8955
9702
  }
@@ -8992,11 +9739,11 @@ class MozGridGroupRowComponent {
8992
9739
  return String(this.groupRow().value ?? '');
8993
9740
  }, ...(ngDevMode ? [{ debugName: "groupValue" }] : /* istanbul ignore next */ []));
8994
9741
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridGroupRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8995
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: MozGridGroupRowComponent, isStandalone: true, selector: "moz-grid-group-row", inputs: { groupRow: { classPropertyName: "groupRow", publicName: "groupRow", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { toggleExpand: "toggleExpand" }, ngImport: i0, template: "<div\n class=\"grid-group-row\"\n role=\"row\"\n [style.padding-left.px]=\"groupRow().depth * 24 + 16\"\n (click)=\"toggleExpand.emit(groupRow().groupKey)\"\n>\n <span\n class=\"grid-group-row__toggle\"\n [class.grid-group-row__toggle--expanded]=\"groupRow().expanded\"\n >\n <ChevronRight20 />\n </span>\n <div class=\"grid-group-row__info\">\n <span class=\"grid-group-row__field\">{{ fieldLabel() }}</span>\n <span class=\"grid-group-row__value\">{{ groupValue() }}</span>\n </div>\n <span class=\"grid-group-row__count\">{{ groupRow().count }}</span>\n</div>\n", styles: [":host{display:block;position:sticky;left:0;overflow:hidden;box-sizing:border-box}.grid-group-row{display:flex;align-items:center;gap:var(--spacing-s, 8px);height:73px;background:var(--color-background-secondary);border-bottom:1px solid var(--color-border-primary);cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.grid-group-row:hover{background:var(--color-background-tertiary, #f0f0f0)}.grid-group-row__toggle{display:flex;align-items:center;color:var(--color-text-secondary);flex-shrink:0;transition:transform .2s ease}.grid-group-row__toggle--expanded{transform:rotate(90deg)}.grid-group-row__info{display:flex;flex-direction:column;gap:2px;min-width:0}.grid-group-row__field{font-size:var(--font-size-xs, 12px);font-weight:500;color:var(--color-text-secondary);text-transform:uppercase;letter-spacing:.5px}.grid-group-row__value{font-size:var(--font-size-l, 18px);font-weight:700;color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.grid-group-row__count{margin-left:auto;align-self:center;font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);font-weight:400;padding-right:16px}\n"], dependencies: [{ kind: "component", type: ChevronRight20, selector: "ChevronRight20", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9742
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: MozGridGroupRowComponent, isStandalone: true, selector: "moz-grid-group-row", inputs: { groupRow: { classPropertyName: "groupRow", publicName: "groupRow", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { toggleExpand: "toggleExpand" }, ngImport: i0, template: "<div\n class=\"grid-group-row\"\n role=\"row\"\n [style.padding-left.px]=\"groupRow().depth * 24 + 16\"\n (click)=\"toggleExpand.emit(groupRow().groupKey)\"\n>\n <span\n class=\"grid-group-row__toggle\"\n [class.grid-group-row__toggle--expanded]=\"groupRow().expanded\"\n >\n <ChevronRight20 />\n </span>\n <div class=\"grid-group-row__info\">\n <span class=\"grid-group-row__field\">{{ fieldLabel() }}</span>\n <span class=\"grid-group-row__value\">{{ groupValue() }}</span>\n </div>\n <span class=\"grid-group-row__count\">{{ groupRow().count }}</span>\n</div>\n", styles: [":host{display:block;position:sticky;left:0;overflow:hidden;box-sizing:border-box}.grid-group-row{display:flex;align-items:center;gap:var(--spacing-s, 8px);height:73px;background:var(--color-background-secondary);border-bottom:1px solid var(--color-border-primary);cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.grid-group-row:hover{background:var(--color-background-tertiary, #f0f0f0)}.grid-group-row__toggle{display:flex;align-items:center;color:var(--color-text-secondary);flex-shrink:0;transition:transform .2s ease}.grid-group-row__toggle--expanded{transform:rotate(90deg)}.grid-group-row__info{display:flex;flex-direction:column;gap:2px;min-width:0}.grid-group-row__field{font-size:var(--font-size-xs, 12px);font-weight:500;color:var(--color-text-secondary);text-transform:uppercase;letter-spacing:.5px}.grid-group-row__value{font-size:var(--font-size-l, 18px);font-weight:700;color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.grid-group-row__count{align-self:center;font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);font-weight:400;padding-right:16px;padding-left:12px}\n"], dependencies: [{ kind: "component", type: ChevronRight20, selector: "ChevronRight20", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8996
9743
  }
8997
9744
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridGroupRowComponent, decorators: [{
8998
9745
  type: Component,
8999
- args: [{ selector: 'moz-grid-group-row', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChevronRight20], template: "<div\n class=\"grid-group-row\"\n role=\"row\"\n [style.padding-left.px]=\"groupRow().depth * 24 + 16\"\n (click)=\"toggleExpand.emit(groupRow().groupKey)\"\n>\n <span\n class=\"grid-group-row__toggle\"\n [class.grid-group-row__toggle--expanded]=\"groupRow().expanded\"\n >\n <ChevronRight20 />\n </span>\n <div class=\"grid-group-row__info\">\n <span class=\"grid-group-row__field\">{{ fieldLabel() }}</span>\n <span class=\"grid-group-row__value\">{{ groupValue() }}</span>\n </div>\n <span class=\"grid-group-row__count\">{{ groupRow().count }}</span>\n</div>\n", styles: [":host{display:block;position:sticky;left:0;overflow:hidden;box-sizing:border-box}.grid-group-row{display:flex;align-items:center;gap:var(--spacing-s, 8px);height:73px;background:var(--color-background-secondary);border-bottom:1px solid var(--color-border-primary);cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.grid-group-row:hover{background:var(--color-background-tertiary, #f0f0f0)}.grid-group-row__toggle{display:flex;align-items:center;color:var(--color-text-secondary);flex-shrink:0;transition:transform .2s ease}.grid-group-row__toggle--expanded{transform:rotate(90deg)}.grid-group-row__info{display:flex;flex-direction:column;gap:2px;min-width:0}.grid-group-row__field{font-size:var(--font-size-xs, 12px);font-weight:500;color:var(--color-text-secondary);text-transform:uppercase;letter-spacing:.5px}.grid-group-row__value{font-size:var(--font-size-l, 18px);font-weight:700;color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.grid-group-row__count{margin-left:auto;align-self:center;font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);font-weight:400;padding-right:16px}\n"] }]
9746
+ args: [{ selector: 'moz-grid-group-row', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChevronRight20], template: "<div\n class=\"grid-group-row\"\n role=\"row\"\n [style.padding-left.px]=\"groupRow().depth * 24 + 16\"\n (click)=\"toggleExpand.emit(groupRow().groupKey)\"\n>\n <span\n class=\"grid-group-row__toggle\"\n [class.grid-group-row__toggle--expanded]=\"groupRow().expanded\"\n >\n <ChevronRight20 />\n </span>\n <div class=\"grid-group-row__info\">\n <span class=\"grid-group-row__field\">{{ fieldLabel() }}</span>\n <span class=\"grid-group-row__value\">{{ groupValue() }}</span>\n </div>\n <span class=\"grid-group-row__count\">{{ groupRow().count }}</span>\n</div>\n", styles: [":host{display:block;position:sticky;left:0;overflow:hidden;box-sizing:border-box}.grid-group-row{display:flex;align-items:center;gap:var(--spacing-s, 8px);height:73px;background:var(--color-background-secondary);border-bottom:1px solid var(--color-border-primary);cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.grid-group-row:hover{background:var(--color-background-tertiary, #f0f0f0)}.grid-group-row__toggle{display:flex;align-items:center;color:var(--color-text-secondary);flex-shrink:0;transition:transform .2s ease}.grid-group-row__toggle--expanded{transform:rotate(90deg)}.grid-group-row__info{display:flex;flex-direction:column;gap:2px;min-width:0}.grid-group-row__field{font-size:var(--font-size-xs, 12px);font-weight:500;color:var(--color-text-secondary);text-transform:uppercase;letter-spacing:.5px}.grid-group-row__value{font-size:var(--font-size-l, 18px);font-weight:700;color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.grid-group-row__count{align-self:center;font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);font-weight:400;padding-right:16px;padding-left:12px}\n"] }]
9000
9747
  }], propDecorators: { groupRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupRow", required: true }] }], toggleExpand: [{ type: i0.Output, args: ["toggleExpand"] }] } });
9001
9748
 
9002
9749
  class MozGridDetailRowComponent {
@@ -9317,7 +10064,7 @@ class GridSettingsDrawerComponent {
9317
10064
  this.searchQuery.set('');
9318
10065
  }
9319
10066
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GridSettingsDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9320
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: GridSettingsDrawerComponent, isStandalone: true, selector: "moz-grid-settings-drawer", ngImport: i0, template: "@switch (screen()) { @case ('main') {\n<div class=\"settings-list\">\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('density')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Data density</span>\n <span class=\"settings-list__item-subtitle\">{{ densityLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('columns')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Display columns</span>\n <span class=\"settings-list__item-subtitle\">{{ columnsLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n</div>\n} @case ('density') {\n<div class=\"settings-density\">\n <label class=\"settings-density__label\">Data density</label>\n <moz-select\n name=\"density\"\n [options]=\"densityOptions\"\n [ngModel]=\"draftDensity()\"\n (ngModelChange)=\"draftDensity.set($event)\"\n />\n</div>\n} @case ('columns') {\n<div class=\"settings-columns\">\n <input\n class=\"settings-columns__search\"\n type=\"text\"\n placeholder=\"Find a column\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n aria-label=\"Search columns\"\n />\n <div class=\"settings-columns__list\" cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\">\n @for (col of filteredColumns(); track col.field) {\n <div class=\"settings-columns__item\" cdkDrag>\n <div class=\"settings-columns__item-left\">\n <span class=\"settings-columns__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"settings-columns__item-label\">{{ col.headerName }}</span>\n </div>\n <moz-toggle\n [id]=\"'col-toggle-' + col.field\"\n [ngModel]=\"col.visible\"\n (ngModelChange)=\"onColumnToggle(col.field, $event)\"\n />\n </div>\n }\n </div>\n <div class=\"settings-columns__bulk-actions\">\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"hideAll()\">\n Hide all\n </button>\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"showAll()\">\n Show all\n </button>\n </div>\n</div>\n} }\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".settings-list{display:flex;flex-direction:column;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-m, 8px);overflow:hidden}.settings-list__item{display:flex;align-items:center;justify-content:space-between;padding:16px;background:var(--color-background-primary);border:none;border-bottom:1px solid var(--color-border-primary);cursor:pointer;text-align:left;width:100%}.settings-list__item:last-child{border-bottom:none}.settings-list__item:hover{background:var(--color-background-secondary)}.settings-list__item-text{display:flex;flex-direction:column;gap:2px}.settings-list__item-title{font-weight:600;font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.settings-list__item-subtitle{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary)}.settings-density{display:flex;flex-direction:column;gap:8px}.settings-density__label{font-weight:600;font-size:var(--font-size-s, 14px);color:var(--color-text-primary)}.settings-columns{display:flex;flex-direction:column;gap:12px}.settings-columns__search{width:100%;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);background:var(--color-background-primary, #fff);box-sizing:border-box}.settings-columns__search::placeholder{color:var(--color-text-secondary)}.settings-columns__search:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.settings-columns__list{display:flex;flex-direction:column}.settings-columns__bulk-actions{display:flex;gap:8px;padding-top:8px;border-top:1px solid var(--color-border-primary)}.settings-columns__bulk-btn{flex:1;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);font-weight:500;color:var(--color-text-primary);cursor:pointer}.settings-columns__bulk-btn:hover{background:var(--color-background-secondary, #f5f5f5)}.settings-columns__item{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.settings-columns__item:last-child{border-bottom:none}.settings-columns__item-left{display:flex;align-items:center;gap:8px}.settings-columns__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary)}.settings-columns__drag-handle:active{cursor:grabbing}.settings-columns__item-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.cdk-drag-preview{display:flex;align-items:center;justify-content:space-between;padding:16px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"], dependencies: [{ 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: "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: "directive", type: MozDrawerFooterDirective, selector: "[mozDrawerFooter]" }, { kind: "component", type: MozSelectComponent, selector: "moz-select", inputs: ["id", "name", "options", "placeholder", "isInvalid", "disabled", "readonly", "size"] }, { kind: "component", type: MozToggleComponent, selector: "moz-toggle", inputs: ["id", "name", "size", "disabled"] }, { kind: "component", type: ChevronRight20, selector: "ChevronRight20", inputs: ["hostClass"] }, { kind: "component", type: Drag20, selector: "Drag20", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10067
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: GridSettingsDrawerComponent, isStandalone: true, selector: "moz-grid-settings-drawer", ngImport: i0, template: "@switch (screen()) { @case ('main') {\n<div class=\"settings-list\">\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('density')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Data density</span>\n <span class=\"settings-list__item-subtitle\">{{ densityLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('columns')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Display columns</span>\n <span class=\"settings-list__item-subtitle\">{{ columnsLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n</div>\n} @case ('density') {\n<div class=\"settings-density\">\n <label class=\"settings-density__label\">Data density</label>\n <moz-select\n name=\"density\"\n [options]=\"densityOptions\"\n [ngModel]=\"draftDensity()\"\n (ngModelChange)=\"draftDensity.set($event)\"\n />\n</div>\n} @case ('columns') {\n<div class=\"settings-columns\">\n <input\n class=\"settings-columns__search\"\n type=\"text\"\n placeholder=\"Find a column\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n aria-label=\"Search columns\"\n />\n <div class=\"settings-columns__list\" cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\">\n @for (col of filteredColumns(); track col.field) {\n <div class=\"settings-columns__item\" cdkDrag>\n <div class=\"settings-columns__item-left\">\n <span class=\"settings-columns__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"settings-columns__item-label\">{{ col.headerName }}</span>\n </div>\n <moz-toggle\n [id]=\"'col-toggle-' + col.field\"\n [ngModel]=\"col.visible\"\n (ngModelChange)=\"onColumnToggle(col.field, $event)\"\n />\n </div>\n }\n </div>\n <div class=\"settings-columns__bulk-actions\">\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"hideAll()\">Hide all</button>\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"showAll()\">Show all</button>\n </div>\n</div>\n} }\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\" [appearance]=\"'accent'\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".settings-list{display:flex;flex-direction:column;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-m, 8px);overflow:hidden}.settings-list__item{display:flex;align-items:center;justify-content:space-between;padding:16px;background:var(--color-background-primary);border:none;border-bottom:1px solid var(--color-border-primary);cursor:pointer;text-align:left;width:100%}.settings-list__item:last-child{border-bottom:none}.settings-list__item:hover{background:var(--color-background-secondary)}.settings-list__item-text{display:flex;flex-direction:column;gap:2px}.settings-list__item-title{font-weight:600;font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.settings-list__item-subtitle{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary)}.settings-density{display:flex;flex-direction:column;gap:8px}.settings-density__label{font-weight:600;font-size:var(--font-size-s, 14px);color:var(--color-text-primary)}.settings-columns{display:flex;flex-direction:column;gap:12px}.settings-columns__search{width:100%;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);background:var(--color-background-primary, #fff);box-sizing:border-box}.settings-columns__search::placeholder{color:var(--color-text-secondary)}.settings-columns__search:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.settings-columns__list{display:flex;flex-direction:column}.settings-columns__bulk-actions{display:flex;gap:8px;padding-top:8px;border-top:1px solid var(--color-border-primary)}.settings-columns__bulk-btn{flex:1;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);font-weight:500;color:var(--color-text-primary);cursor:pointer}.settings-columns__bulk-btn:hover{background:var(--color-background-secondary, #f5f5f5)}.settings-columns__item{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.settings-columns__item:last-child{border-bottom:none}.settings-columns__item-left{display:flex;align-items:center;gap:8px}.settings-columns__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary)}.settings-columns__drag-handle:active{cursor:grabbing}.settings-columns__item-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.cdk-drag-preview{display:flex;align-items:center;justify-content:space-between;padding:16px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"], dependencies: [{ 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: "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: "directive", type: MozDrawerFooterDirective, selector: "[mozDrawerFooter]" }, { kind: "component", type: MozSelectComponent, selector: "moz-select", inputs: ["id", "name", "options", "placeholder", "isInvalid", "disabled", "readonly", "size"] }, { kind: "component", type: MozToggleComponent, selector: "moz-toggle", inputs: ["id", "name", "size", "disabled"] }, { kind: "component", type: ChevronRight20, selector: "ChevronRight20", inputs: ["hostClass"] }, { kind: "component", type: Drag20, selector: "Drag20", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9321
10068
  }
9322
10069
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GridSettingsDrawerComponent, decorators: [{
9323
10070
  type: Component,
@@ -9332,7 +10079,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
9332
10079
  MozToggleComponent,
9333
10080
  ChevronRight20,
9334
10081
  Drag20,
9335
- ], template: "@switch (screen()) { @case ('main') {\n<div class=\"settings-list\">\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('density')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Data density</span>\n <span class=\"settings-list__item-subtitle\">{{ densityLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('columns')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Display columns</span>\n <span class=\"settings-list__item-subtitle\">{{ columnsLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n</div>\n} @case ('density') {\n<div class=\"settings-density\">\n <label class=\"settings-density__label\">Data density</label>\n <moz-select\n name=\"density\"\n [options]=\"densityOptions\"\n [ngModel]=\"draftDensity()\"\n (ngModelChange)=\"draftDensity.set($event)\"\n />\n</div>\n} @case ('columns') {\n<div class=\"settings-columns\">\n <input\n class=\"settings-columns__search\"\n type=\"text\"\n placeholder=\"Find a column\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n aria-label=\"Search columns\"\n />\n <div class=\"settings-columns__list\" cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\">\n @for (col of filteredColumns(); track col.field) {\n <div class=\"settings-columns__item\" cdkDrag>\n <div class=\"settings-columns__item-left\">\n <span class=\"settings-columns__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"settings-columns__item-label\">{{ col.headerName }}</span>\n </div>\n <moz-toggle\n [id]=\"'col-toggle-' + col.field\"\n [ngModel]=\"col.visible\"\n (ngModelChange)=\"onColumnToggle(col.field, $event)\"\n />\n </div>\n }\n </div>\n <div class=\"settings-columns__bulk-actions\">\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"hideAll()\">\n Hide all\n </button>\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"showAll()\">\n Show all\n </button>\n </div>\n</div>\n} }\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".settings-list{display:flex;flex-direction:column;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-m, 8px);overflow:hidden}.settings-list__item{display:flex;align-items:center;justify-content:space-between;padding:16px;background:var(--color-background-primary);border:none;border-bottom:1px solid var(--color-border-primary);cursor:pointer;text-align:left;width:100%}.settings-list__item:last-child{border-bottom:none}.settings-list__item:hover{background:var(--color-background-secondary)}.settings-list__item-text{display:flex;flex-direction:column;gap:2px}.settings-list__item-title{font-weight:600;font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.settings-list__item-subtitle{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary)}.settings-density{display:flex;flex-direction:column;gap:8px}.settings-density__label{font-weight:600;font-size:var(--font-size-s, 14px);color:var(--color-text-primary)}.settings-columns{display:flex;flex-direction:column;gap:12px}.settings-columns__search{width:100%;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);background:var(--color-background-primary, #fff);box-sizing:border-box}.settings-columns__search::placeholder{color:var(--color-text-secondary)}.settings-columns__search:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.settings-columns__list{display:flex;flex-direction:column}.settings-columns__bulk-actions{display:flex;gap:8px;padding-top:8px;border-top:1px solid var(--color-border-primary)}.settings-columns__bulk-btn{flex:1;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);font-weight:500;color:var(--color-text-primary);cursor:pointer}.settings-columns__bulk-btn:hover{background:var(--color-background-secondary, #f5f5f5)}.settings-columns__item{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.settings-columns__item:last-child{border-bottom:none}.settings-columns__item-left{display:flex;align-items:center;gap:8px}.settings-columns__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary)}.settings-columns__drag-handle:active{cursor:grabbing}.settings-columns__item-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.cdk-drag-preview{display:flex;align-items:center;justify-content:space-between;padding:16px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"] }]
10082
+ ], template: "@switch (screen()) { @case ('main') {\n<div class=\"settings-list\">\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('density')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Data density</span>\n <span class=\"settings-list__item-subtitle\">{{ densityLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n <button type=\"button\" class=\"settings-list__item\" (click)=\"goTo('columns')\">\n <div class=\"settings-list__item-text\">\n <span class=\"settings-list__item-title\">Display columns</span>\n <span class=\"settings-list__item-subtitle\">{{ columnsLabel() }}</span>\n </div>\n <ChevronRight20 />\n </button>\n</div>\n} @case ('density') {\n<div class=\"settings-density\">\n <label class=\"settings-density__label\">Data density</label>\n <moz-select\n name=\"density\"\n [options]=\"densityOptions\"\n [ngModel]=\"draftDensity()\"\n (ngModelChange)=\"draftDensity.set($event)\"\n />\n</div>\n} @case ('columns') {\n<div class=\"settings-columns\">\n <input\n class=\"settings-columns__search\"\n type=\"text\"\n placeholder=\"Find a column\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n aria-label=\"Search columns\"\n />\n <div class=\"settings-columns__list\" cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\">\n @for (col of filteredColumns(); track col.field) {\n <div class=\"settings-columns__item\" cdkDrag>\n <div class=\"settings-columns__item-left\">\n <span class=\"settings-columns__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"settings-columns__item-label\">{{ col.headerName }}</span>\n </div>\n <moz-toggle\n [id]=\"'col-toggle-' + col.field\"\n [ngModel]=\"col.visible\"\n (ngModelChange)=\"onColumnToggle(col.field, $event)\"\n />\n </div>\n }\n </div>\n <div class=\"settings-columns__bulk-actions\">\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"hideAll()\">Hide all</button>\n <button type=\"button\" class=\"settings-columns__bulk-btn\" (click)=\"showAll()\">Show all</button>\n </div>\n</div>\n} }\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\" [appearance]=\"'accent'\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".settings-list{display:flex;flex-direction:column;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-m, 8px);overflow:hidden}.settings-list__item{display:flex;align-items:center;justify-content:space-between;padding:16px;background:var(--color-background-primary);border:none;border-bottom:1px solid var(--color-border-primary);cursor:pointer;text-align:left;width:100%}.settings-list__item:last-child{border-bottom:none}.settings-list__item:hover{background:var(--color-background-secondary)}.settings-list__item-text{display:flex;flex-direction:column;gap:2px}.settings-list__item-title{font-weight:600;font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.settings-list__item-subtitle{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary)}.settings-density{display:flex;flex-direction:column;gap:8px}.settings-density__label{font-weight:600;font-size:var(--font-size-s, 14px);color:var(--color-text-primary)}.settings-columns{display:flex;flex-direction:column;gap:12px}.settings-columns__search{width:100%;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);background:var(--color-background-primary, #fff);box-sizing:border-box}.settings-columns__search::placeholder{color:var(--color-text-secondary)}.settings-columns__search:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.settings-columns__list{display:flex;flex-direction:column}.settings-columns__bulk-actions{display:flex;gap:8px;padding-top:8px;border-top:1px solid var(--color-border-primary)}.settings-columns__bulk-btn{flex:1;padding:8px 12px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);font-weight:500;color:var(--color-text-primary);cursor:pointer}.settings-columns__bulk-btn:hover{background:var(--color-background-secondary, #f5f5f5)}.settings-columns__item{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.settings-columns__item:last-child{border-bottom:none}.settings-columns__item-left{display:flex;align-items:center;gap:8px}.settings-columns__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary)}.settings-columns__drag-handle:active{cursor:grabbing}.settings-columns__item-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.cdk-drag-preview{display:flex;align-items:center;justify-content:space-between;padding:16px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"] }]
9336
10083
  }], ctorParameters: () => [] });
9337
10084
 
9338
10085
  class GridGroupDrawerComponent {
@@ -9384,7 +10131,7 @@ class GridGroupDrawerComponent {
9384
10131
  this.draftGrouped.set([]);
9385
10132
  }
9386
10133
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GridGroupDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9387
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: GridGroupDrawerComponent, isStandalone: true, selector: "moz-grid-group-drawer", ngImport: i0, template: "<div class=\"group-drawer__list\" cdkDropList (cdkDropListDropped)=\"onDrop($event)\">\n @for (item of draftGrouped(); track item.field; let idx = $index) {\n <div class=\"group-drawer__item\" cdkDrag>\n <span class=\"group-drawer__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"group-drawer__item-label\">{{ item.headerName }}</span>\n <select\n class=\"group-drawer__sort-select\"\n [value]=\"item.sortDirection\"\n [attr.aria-label]=\"'Sort direction for ' + item.headerName\"\n (change)=\"onSortDirectionChange(idx, $event)\"\n >\n <option value=\"asc\">A \u2192 Z</option>\n <option value=\"desc\">Z \u2192 A</option>\n </select>\n <button\n type=\"button\"\n moz-button\n [attr.aria-label]=\"'Remove ' + item.headerName\"\n (click)=\"removeGroup(item.field)\"\n [iconPosition]=\"'only'\"\n [size]=\"'s'\"\n [ghost]=\"true\"\n >\n <Cross20 icon />\n </button>\n </div>\n }\n</div>\n\n@if (available().length > 0) {\n<div class=\"group-drawer__available\">\n <h4 class=\"group-drawer__section-title\">Available columns</h4>\n @for (col of available(); track col.field) {\n <div class=\"group-drawer__available-item\">\n <span class=\"group-drawer__available-label\">{{ col.headerName }}</span>\n <button\n type=\"button\"\n class=\"group-drawer__add-btn\"\n [attr.aria-label]=\"'Add ' + col.headerName + ' as group'\"\n (click)=\"addGroup(col.field)\"\n >\n <ListAdd20 />\n </button>\n </div>\n }\n</div>\n} @if (draftGrouped().length === 0 && available().length === 0) {\n<p class=\"group-drawer__empty\">No groupable columns available.</p>\n}\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".group-drawer__list{display:flex;flex-direction:column}.group-drawer__item{display:flex;align-items:center;gap:8px;padding:12px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.group-drawer__item:last-child{border-bottom:none}.group-drawer__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary);flex-shrink:0}.group-drawer__drag-handle:active{cursor:grabbing}.group-drawer__item-label{flex:1;font-size:var(--font-size-m, 16px);color:var(--color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.group-drawer__sort-select{flex-shrink:0;padding:4px 8px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);cursor:pointer}.group-drawer__sort-select:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.group-drawer__available{margin-top:16px;margin-bottom:32px}.group-drawer__section-title{font-size:var(--font-size-s, 14px);font-weight:600;color:var(--color-text-secondary);margin:0 0 8px;text-transform:uppercase;letter-spacing:.5px}.group-drawer__available-item{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--color-border-primary)}.group-drawer__available-item:last-child{border-bottom:none}.group-drawer__available-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.group-drawer__add-btn{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-background-accent-inverse)}.group-drawer__add-btn:hover{background:#1a73e814;color:var(--color-primary-dark, #1557b0)}.group-drawer__empty{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);text-align:center;padding:24px 0}.cdk-drag-preview{display:flex;align-items:center;gap:8px;padding:12px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"], dependencies: [{ 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: "directive", type: MozDrawerFooterDirective, selector: "[mozDrawerFooter]" }, { 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 });
10134
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: GridGroupDrawerComponent, isStandalone: true, selector: "moz-grid-group-drawer", ngImport: i0, template: "<div class=\"group-drawer__list\" cdkDropList (cdkDropListDropped)=\"onDrop($event)\">\n @for (item of draftGrouped(); track item.field; let idx = $index) {\n <div class=\"group-drawer__item\" cdkDrag>\n <span class=\"group-drawer__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"group-drawer__item-label\">{{ item.headerName }}</span>\n <select\n class=\"group-drawer__sort-select\"\n [value]=\"item.sortDirection\"\n [attr.aria-label]=\"'Sort direction for ' + item.headerName\"\n (change)=\"onSortDirectionChange(idx, $event)\"\n >\n <option value=\"asc\">A \u2192 Z</option>\n <option value=\"desc\">Z \u2192 A</option>\n </select>\n <button\n type=\"button\"\n moz-button\n [attr.aria-label]=\"'Remove ' + item.headerName\"\n (click)=\"removeGroup(item.field)\"\n [iconPosition]=\"'only'\"\n [size]=\"'s'\"\n [ghost]=\"true\"\n >\n <Cross20 icon />\n </button>\n </div>\n }\n</div>\n\n@if (available().length > 0) {\n<div class=\"group-drawer__available\">\n <h4 class=\"group-drawer__section-title\">Available columns</h4>\n @for (col of available(); track col.field) {\n <div class=\"group-drawer__available-item\">\n <span class=\"group-drawer__available-label\">{{ col.headerName }}</span>\n <button\n type=\"button\"\n class=\"group-drawer__add-btn\"\n [attr.aria-label]=\"'Add ' + col.headerName + ' as group'\"\n (click)=\"addGroup(col.field)\"\n >\n <ListAdd20 />\n </button>\n </div>\n }\n</div>\n} @if (draftGrouped().length === 0 && available().length === 0) {\n<p class=\"group-drawer__empty\">No groupable columns available.</p>\n}\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\" [appearance]=\"'accent'\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".group-drawer__list{display:flex;flex-direction:column}.group-drawer__item{display:flex;align-items:center;gap:8px;padding:12px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.group-drawer__item:last-child{border-bottom:none}.group-drawer__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary);flex-shrink:0}.group-drawer__drag-handle:active{cursor:grabbing}.group-drawer__item-label{flex:1;font-size:var(--font-size-m, 16px);color:var(--color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.group-drawer__sort-select{flex-shrink:0;padding:4px 8px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);cursor:pointer}.group-drawer__sort-select:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.group-drawer__available{margin-top:16px;margin-bottom:32px}.group-drawer__section-title{font-size:var(--font-size-s, 14px);font-weight:600;color:var(--color-text-secondary);margin:0 0 8px;text-transform:uppercase;letter-spacing:.5px}.group-drawer__available-item{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--color-border-primary)}.group-drawer__available-item:last-child{border-bottom:none}.group-drawer__available-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.group-drawer__add-btn{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-background-accent-inverse)}.group-drawer__add-btn:hover{background:#1a73e814;color:var(--color-primary-dark, #1557b0)}.group-drawer__empty{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);text-align:center;padding:24px 0}.cdk-drag-preview{display:flex;align-items:center;gap:8px;padding:12px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"], dependencies: [{ 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: "directive", type: MozDrawerFooterDirective, selector: "[mozDrawerFooter]" }, { 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 });
9388
10135
  }
9389
10136
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GridGroupDrawerComponent, decorators: [{
9390
10137
  type: Component,
@@ -9397,7 +10144,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
9397
10144
  Drag20,
9398
10145
  Cross20,
9399
10146
  ListAdd20,
9400
- ], template: "<div class=\"group-drawer__list\" cdkDropList (cdkDropListDropped)=\"onDrop($event)\">\n @for (item of draftGrouped(); track item.field; let idx = $index) {\n <div class=\"group-drawer__item\" cdkDrag>\n <span class=\"group-drawer__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"group-drawer__item-label\">{{ item.headerName }}</span>\n <select\n class=\"group-drawer__sort-select\"\n [value]=\"item.sortDirection\"\n [attr.aria-label]=\"'Sort direction for ' + item.headerName\"\n (change)=\"onSortDirectionChange(idx, $event)\"\n >\n <option value=\"asc\">A \u2192 Z</option>\n <option value=\"desc\">Z \u2192 A</option>\n </select>\n <button\n type=\"button\"\n moz-button\n [attr.aria-label]=\"'Remove ' + item.headerName\"\n (click)=\"removeGroup(item.field)\"\n [iconPosition]=\"'only'\"\n [size]=\"'s'\"\n [ghost]=\"true\"\n >\n <Cross20 icon />\n </button>\n </div>\n }\n</div>\n\n@if (available().length > 0) {\n<div class=\"group-drawer__available\">\n <h4 class=\"group-drawer__section-title\">Available columns</h4>\n @for (col of available(); track col.field) {\n <div class=\"group-drawer__available-item\">\n <span class=\"group-drawer__available-label\">{{ col.headerName }}</span>\n <button\n type=\"button\"\n class=\"group-drawer__add-btn\"\n [attr.aria-label]=\"'Add ' + col.headerName + ' as group'\"\n (click)=\"addGroup(col.field)\"\n >\n <ListAdd20 />\n </button>\n </div>\n }\n</div>\n} @if (draftGrouped().length === 0 && available().length === 0) {\n<p class=\"group-drawer__empty\">No groupable columns available.</p>\n}\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".group-drawer__list{display:flex;flex-direction:column}.group-drawer__item{display:flex;align-items:center;gap:8px;padding:12px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.group-drawer__item:last-child{border-bottom:none}.group-drawer__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary);flex-shrink:0}.group-drawer__drag-handle:active{cursor:grabbing}.group-drawer__item-label{flex:1;font-size:var(--font-size-m, 16px);color:var(--color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.group-drawer__sort-select{flex-shrink:0;padding:4px 8px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);cursor:pointer}.group-drawer__sort-select:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.group-drawer__available{margin-top:16px;margin-bottom:32px}.group-drawer__section-title{font-size:var(--font-size-s, 14px);font-weight:600;color:var(--color-text-secondary);margin:0 0 8px;text-transform:uppercase;letter-spacing:.5px}.group-drawer__available-item{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--color-border-primary)}.group-drawer__available-item:last-child{border-bottom:none}.group-drawer__available-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.group-drawer__add-btn{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-background-accent-inverse)}.group-drawer__add-btn:hover{background:#1a73e814;color:var(--color-primary-dark, #1557b0)}.group-drawer__empty{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);text-align:center;padding:24px 0}.cdk-drag-preview{display:flex;align-items:center;gap:8px;padding:12px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"] }]
10147
+ ], template: "<div class=\"group-drawer__list\" cdkDropList (cdkDropListDropped)=\"onDrop($event)\">\n @for (item of draftGrouped(); track item.field; let idx = $index) {\n <div class=\"group-drawer__item\" cdkDrag>\n <span class=\"group-drawer__drag-handle\" cdkDragHandle>\n <Drag20 />\n </span>\n <span class=\"group-drawer__item-label\">{{ item.headerName }}</span>\n <select\n class=\"group-drawer__sort-select\"\n [value]=\"item.sortDirection\"\n [attr.aria-label]=\"'Sort direction for ' + item.headerName\"\n (change)=\"onSortDirectionChange(idx, $event)\"\n >\n <option value=\"asc\">A \u2192 Z</option>\n <option value=\"desc\">Z \u2192 A</option>\n </select>\n <button\n type=\"button\"\n moz-button\n [attr.aria-label]=\"'Remove ' + item.headerName\"\n (click)=\"removeGroup(item.field)\"\n [iconPosition]=\"'only'\"\n [size]=\"'s'\"\n [ghost]=\"true\"\n >\n <Cross20 icon />\n </button>\n </div>\n }\n</div>\n\n@if (available().length > 0) {\n<div class=\"group-drawer__available\">\n <h4 class=\"group-drawer__section-title\">Available columns</h4>\n @for (col of available(); track col.field) {\n <div class=\"group-drawer__available-item\">\n <span class=\"group-drawer__available-label\">{{ col.headerName }}</span>\n <button\n type=\"button\"\n class=\"group-drawer__add-btn\"\n [attr.aria-label]=\"'Add ' + col.headerName + ' as group'\"\n (click)=\"addGroup(col.field)\"\n >\n <ListAdd20 />\n </button>\n </div>\n }\n</div>\n} @if (draftGrouped().length === 0 && available().length === 0) {\n<p class=\"group-drawer__empty\">No groupable columns available.</p>\n}\n\n<ng-template mozDrawerFooter>\n <button moz-button (click)=\"apply()\" [appearance]=\"'accent'\">Apply</button>\n <button moz-button [outlined]=\"true\" (click)=\"reset()\">Reset</button>\n</ng-template>\n", styles: [".group-drawer__list{display:flex;flex-direction:column}.group-drawer__item{display:flex;align-items:center;gap:8px;padding:12px 0;border-bottom:1px solid var(--color-border-primary);background:var(--color-background-primary, #fff)}.group-drawer__item:last-child{border-bottom:none}.group-drawer__drag-handle{display:flex;align-items:center;cursor:grab;color:var(--color-text-secondary);flex-shrink:0}.group-drawer__drag-handle:active{cursor:grabbing}.group-drawer__item-label{flex:1;font-size:var(--font-size-m, 16px);color:var(--color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.group-drawer__sort-select{flex-shrink:0;padding:4px 8px;border:1px solid var(--color-border-primary);border-radius:var(--border-radius-s, 4px);background:var(--color-background-primary, #fff);font-size:var(--font-size-s, 14px);color:var(--color-text-primary);cursor:pointer}.group-drawer__sort-select:focus{outline:2px solid var(--color-background-accent-inverse);outline-offset:-1px}.group-drawer__available{margin-top:16px;margin-bottom:32px}.group-drawer__section-title{font-size:var(--font-size-s, 14px);font-weight:600;color:var(--color-text-secondary);margin:0 0 8px;text-transform:uppercase;letter-spacing:.5px}.group-drawer__available-item{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--color-border-primary)}.group-drawer__available-item:last-child{border-bottom:none}.group-drawer__available-label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary)}.group-drawer__add-btn{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-background-accent-inverse)}.group-drawer__add-btn:hover{background:#1a73e814;color:var(--color-primary-dark, #1557b0)}.group-drawer__empty{font-size:var(--font-size-s, 14px);color:var(--color-text-secondary);text-align:center;padding:24px 0}.cdk-drag-preview{display:flex;align-items:center;gap:8px;padding:12px 8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-border-primary);border-radius:4px;box-shadow:0 4px 12px #00000026;font-size:var(--font-size-m, 16px)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s ease}\n"] }]
9401
10148
  }] });
9402
10149
 
9403
10150
  const EXCEL_SHORTCUTS = [
@@ -9479,6 +10226,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
9479
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"] }]
9480
10227
  }] });
9481
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
+
9482
10257
  class MozGridComponent {
9483
10258
  state = inject(GridStateManager);
9484
10259
  gridEngine = inject(GridEngine);
@@ -9546,7 +10321,28 @@ class MozGridComponent {
9546
10321
  // --- Internal ---
9547
10322
  isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : /* istanbul ignore next */ []));
9548
10323
  groupPanelOpen = signal(false, ...(ngDevMode ? [{ debugName: "groupPanelOpen" }] : /* istanbul ignore next */ []));
9549
- 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 */ []));
9550
10346
  hasHiddenColumns = computed(() => this.state.columnStates().some((col) => !col.visible), ...(ngDevMode ? [{ debugName: "hasHiddenColumns" }] : /* istanbul ignore next */ []));
9551
10347
  hiddenColumnsList = computed(() => {
9552
10348
  const defMap = this.state.columnDefMap();
@@ -9737,7 +10533,7 @@ class MozGridComponent {
9737
10533
  this.persistenceEngine.restore(key);
9738
10534
  }
9739
10535
  }, { allowSignalWrites: true });
9740
- // Auto-save state on column/sort changes
10536
+ // Auto-save state on column/sort/filter changes
9741
10537
  effect(() => {
9742
10538
  const key = this.stateKey();
9743
10539
  if (!key)
@@ -9745,8 +10541,19 @@ class MozGridComponent {
9745
10541
  // Read signals to track them
9746
10542
  this.state.columnStates();
9747
10543
  this.state.activeSorts();
10544
+ this.state.filterModel();
9748
10545
  this.persistenceEngine.save(key);
9749
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
+ });
9750
10557
  // Initialize plugins
9751
10558
  effect(() => {
9752
10559
  const pluginList = this.plugins();
@@ -10013,8 +10820,6 @@ class MozGridComponent {
10013
10820
  const range = this.cellSelectionEngine.getNormalizedRange();
10014
10821
  if (!range)
10015
10822
  return;
10016
- if (this.state.mode() !== 'client')
10017
- return;
10018
10823
  const changes = this.clipboardEngine.fillDown(range);
10019
10824
  if (changes.length === 0)
10020
10825
  return;
@@ -10025,8 +10830,6 @@ class MozGridComponent {
10025
10830
  const range = this.cellSelectionEngine.getNormalizedRange();
10026
10831
  if (!range)
10027
10832
  return;
10028
- if (this.state.mode() !== 'client')
10029
- return;
10030
10833
  const changes = this.clipboardEngine.fillRight(range);
10031
10834
  if (changes.length === 0)
10032
10835
  return;
@@ -10121,37 +10924,35 @@ class MozGridComponent {
10121
10924
  const affected = maxRow - minRow;
10122
10925
  if (affected === 0)
10123
10926
  return;
10124
- if (this.state.mode() === 'client') {
10125
- // Build a map of display index → sourceData index for the fill range
10126
- const indexMap = new Map();
10127
- for (let r = minRow; r <= maxRow; r++) {
10128
- if (r === anchor.row)
10927
+ // Build a map of display index → sourceData index for the fill range
10928
+ const indexMap = new Map();
10929
+ for (let r = minRow; r <= maxRow; r++) {
10930
+ if (r === anchor.row)
10931
+ continue;
10932
+ const srcIdx = this.displayIndexToSourceIndex(r);
10933
+ if (srcIdx >= 0)
10934
+ indexMap.set(r, srcIdx);
10935
+ }
10936
+ const changes = [];
10937
+ this.state.sourceData.update((d) => {
10938
+ const updated = [...d];
10939
+ for (const [, srcIdx] of indexMap) {
10940
+ if (!updated[srcIdx])
10129
10941
  continue;
10130
- const srcIdx = this.displayIndexToSourceIndex(r);
10131
- if (srcIdx >= 0)
10132
- indexMap.set(r, srcIdx);
10133
- }
10134
- const changes = [];
10135
- this.state.sourceData.update((d) => {
10136
- const updated = [...d];
10137
- for (const [, srcIdx] of indexMap) {
10138
- if (!updated[srcIdx])
10139
- continue;
10140
- const before = updated[srcIdx][field];
10141
- if (before === sourceValue)
10142
- continue;
10143
- const rowCopy = { ...updated[srcIdx] };
10144
- rowCopy[field] = sourceValue;
10145
- updated[srcIdx] = rowCopy;
10146
- changes.push({ rowIndex: srcIdx, field, before, after: sourceValue });
10147
- }
10148
- return updated;
10149
- });
10150
- if (changes.length > 0) {
10151
- this.historyEngine.record('fill', changes);
10942
+ const before = updated[srcIdx][field];
10943
+ if (before === sourceValue)
10944
+ continue;
10945
+ const rowCopy = { ...updated[srcIdx] };
10946
+ rowCopy[field] = sourceValue;
10947
+ updated[srcIdx] = rowCopy;
10948
+ changes.push({ rowIndex: srcIdx, field, before, after: sourceValue });
10152
10949
  }
10153
- this.clipboardEngine.clearCut();
10950
+ return updated;
10951
+ });
10952
+ if (changes.length > 0) {
10953
+ this.historyEngine.record('fill', changes);
10154
10954
  }
10955
+ this.clipboardEngine.clearCut();
10155
10956
  this.fillDown.emit({
10156
10957
  sourceCell: anchor,
10157
10958
  sourceValue,
@@ -10190,29 +10991,27 @@ class MozGridComponent {
10190
10991
  }
10191
10992
  if (targetFields.length === 0)
10192
10993
  return;
10193
- if (this.state.mode() === 'client') {
10194
- const changes = [];
10195
- this.state.sourceData.update((d) => {
10196
- const updated = [...d];
10197
- const src = updated[anchorSourceIdx];
10198
- if (!src)
10199
- return updated;
10200
- const rowCopy = { ...src };
10201
- for (const f of targetFields) {
10202
- const before = src[f];
10203
- if (before === sourceValue)
10204
- continue;
10205
- rowCopy[f] = sourceValue;
10206
- changes.push({ rowIndex: anchorSourceIdx, field: f, before, after: sourceValue });
10207
- }
10208
- updated[anchorSourceIdx] = rowCopy;
10994
+ const changes = [];
10995
+ this.state.sourceData.update((d) => {
10996
+ const updated = [...d];
10997
+ const src = updated[anchorSourceIdx];
10998
+ if (!src)
10209
10999
  return updated;
10210
- });
10211
- if (changes.length > 0) {
10212
- this.historyEngine.record('fill', changes);
11000
+ const rowCopy = { ...src };
11001
+ for (const f of targetFields) {
11002
+ const before = src[f];
11003
+ if (before === sourceValue)
11004
+ continue;
11005
+ rowCopy[f] = sourceValue;
11006
+ changes.push({ rowIndex: anchorSourceIdx, field: f, before, after: sourceValue });
10213
11007
  }
10214
- this.clipboardEngine.clearCut();
11008
+ updated[anchorSourceIdx] = rowCopy;
11009
+ return updated;
11010
+ });
11011
+ if (changes.length > 0) {
11012
+ this.historyEngine.record('fill', changes);
10215
11013
  }
11014
+ this.clipboardEngine.clearCut();
10216
11015
  this.fillDown.emit({
10217
11016
  sourceCell: anchor,
10218
11017
  sourceValue,
@@ -10252,17 +11051,27 @@ class MozGridComponent {
10252
11051
  this.state.activeSelectionMode.set('none');
10253
11052
  this.selectionChange.emit(this.rowSelectionEngine.getSelectionEvent());
10254
11053
  }
10255
- onRemoveFilter(field) {
10256
- this.filterEngine.removeFilter(field);
10257
- this.filterChange.emit({ filters: this.state.activeFilters() });
10258
- this.resetInfiniteScrollIfActive();
11054
+ /** Removes a single condition by id (tag "×" button). */
11055
+ onRemoveFilter(id) {
11056
+ this.filterEngine.removeCondition(id);
10259
11057
  }
11058
+ /** Clears the whole filter model ("Remove all" button). */
10260
11059
  onRemoveAllFilters() {
10261
- for (const filter of this.state.activeFilters()) {
10262
- this.filterEngine.removeFilter(filter.field);
10263
- }
10264
- this.filterChange.emit({ filters: this.state.activeFilters() });
10265
- 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
+ });
10266
11075
  }
10267
11076
  onRestoreColumn(field) {
10268
11077
  this.state.updateColumnState(field, { visible: true });
@@ -10457,8 +11266,9 @@ class MozGridComponent {
10457
11266
  try {
10458
11267
  const text = await navigator.clipboard.readText();
10459
11268
  const pasteRows = text.split('\n').map((line) => line.split('\t'));
10460
- if (this.state.mode() === 'client') {
10461
- this.applyPasteToSelectedRows(event.selectedRows, pasteRows);
11269
+ const changes = this.applyPasteToSelectedRows(event.selectedRows, pasteRows);
11270
+ if (changes.length > 0) {
11271
+ this.historyEngine.record('paste', changes);
10462
11272
  }
10463
11273
  this.bulkPaste.emit({
10464
11274
  range: null,
@@ -10480,28 +11290,42 @@ class MozGridComponent {
10480
11290
  return;
10481
11291
  try {
10482
11292
  const text = await navigator.clipboard.readText();
10483
- 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'));
10484
11297
  const cols = this.state.visibleColumns();
10485
- // Build a paste range starting from the focused cell
10486
- const pasteRange = {
10487
- start: { row: focused.row, col: focused.col },
10488
- end: {
10489
- row: Math.min(focused.row + rows.length - 1, this.state.sourceData().length - 1),
10490
- col: Math.min(focused.col + (rows[0]?.length ?? 1) - 1, cols.length - 1),
10491
- },
10492
- };
10493
- if (this.state.mode() === 'client') {
10494
- // If a cut is pending, first wipe the source cells so cut+paste == move,
10495
- // and fold both halves into a single undoable history op.
10496
- const cutSource = this.state.cutSource();
10497
- const clearChanges = cutSource ? this.clipboardEngine.clearRange(cutSource) : [];
10498
- const pasteChanges = this.clipboardEngine.applyPaste(pasteRange, rows);
10499
- const allChanges = [...clearChanges, ...pasteChanges];
10500
- if (allChanges.length > 0) {
10501
- this.historyEngine.record(cutSource ? 'cut' : 'paste', allChanges);
10502
- }
10503
- this.clipboardEngine.clearCut();
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);
10504
11319
  }
11320
+ // If a cut is pending, first wipe the source cells so cut+paste == move,
11321
+ // and fold both halves into a single undoable history op.
11322
+ const cutSource = this.state.cutSource();
11323
+ const clearChanges = cutSource ? this.clipboardEngine.clearRange(cutSource) : [];
11324
+ const allChanges = [...clearChanges, ...pasteChanges];
11325
+ if (allChanges.length > 0) {
11326
+ this.historyEngine.record(cutSource ? 'cut' : 'paste', allChanges);
11327
+ }
11328
+ this.clipboardEngine.clearCut();
10505
11329
  this.bulkPaste.emit({
10506
11330
  range: pasteRange,
10507
11331
  data: rows,
@@ -10541,9 +11365,12 @@ class MozGridComponent {
10541
11365
  onBulkDelete() {
10542
11366
  if (this.state.activeSelectionMode() === 'rows') {
10543
11367
  const event = this.rowSelectionEngine.getSelectionEvent();
10544
- if (this.state.mode() === 'client') {
10545
- this.deleteSelectedRows(event.selectedRows);
11368
+ const changes = this.deleteSelectedRows(event.selectedRows);
11369
+ if (changes.length > 0) {
11370
+ this.historyEngine.record('delete', changes);
10546
11371
  }
11372
+ // Intentionally keep the selection active so users can chain actions
11373
+ // (undo, then paste, etc.) — the overlay only closes via the ✕ button.
10547
11374
  this.bulkDelete.emit({
10548
11375
  range: null,
10549
11376
  cellCount: event.count,
@@ -10564,13 +11391,11 @@ class MozGridComponent {
10564
11391
  return;
10565
11392
  const rows = range.end.row - range.start.row + 1;
10566
11393
  const colCount = range.end.col - range.start.col + 1;
10567
- if (this.state.mode() === 'client') {
10568
- const changes = this.clipboardEngine.clearRange(range);
10569
- if (changes.length > 0) {
10570
- this.historyEngine.record('delete', changes);
10571
- }
10572
- this.clipboardEngine.clearCut();
11394
+ const changes = this.clipboardEngine.clearRange(range);
11395
+ if (changes.length > 0) {
11396
+ this.historyEngine.record('delete', changes);
10573
11397
  }
11398
+ this.clipboardEngine.clearCut();
10574
11399
  this.bulkDelete.emit({
10575
11400
  range,
10576
11401
  cellCount: rows * colCount,
@@ -10623,6 +11448,7 @@ class MozGridComponent {
10623
11448
  applyPasteToSelectedRows(selectedRows, pasteRows) {
10624
11449
  const cols = this.state.visibleColumns();
10625
11450
  const idField = this.state.rowIdField?.() ?? 'id';
11451
+ const changes = [];
10626
11452
  this.state.sourceData.update((data) => {
10627
11453
  const updated = [...data];
10628
11454
  for (let ri = 0; ri < Math.min(selectedRows.length, pasteRows.length); ri++) {
@@ -10638,10 +11464,14 @@ class MozGridComponent {
10638
11464
  if (!field)
10639
11465
  continue;
10640
11466
  const coerced = this.coerceAndValidate(field, pasteRows[ri][ci], updated[dataIndex]);
10641
- if (coerced !== PASTE_SKIP) {
10642
- rowCopy[field] = coerced;
10643
- changed = true;
10644
- }
11467
+ if (coerced === PASTE_SKIP)
11468
+ continue;
11469
+ const before = updated[dataIndex][field];
11470
+ if (before === coerced)
11471
+ continue;
11472
+ rowCopy[field] = coerced;
11473
+ changes.push({ rowIndex: dataIndex, field, before, after: coerced });
11474
+ changed = true;
10645
11475
  }
10646
11476
  if (changed) {
10647
11477
  updated[dataIndex] = rowCopy;
@@ -10649,10 +11479,12 @@ class MozGridComponent {
10649
11479
  }
10650
11480
  return updated;
10651
11481
  });
11482
+ return changes;
10652
11483
  }
10653
11484
  deleteSelectedRows(selectedRows) {
10654
11485
  const cols = this.state.visibleColumns();
10655
11486
  const idField = this.state.rowIdField?.() ?? 'id';
11487
+ const changes = [];
10656
11488
  this.state.sourceData.update((data) => {
10657
11489
  const updated = [...data];
10658
11490
  for (const selectedRow of selectedRows) {
@@ -10664,10 +11496,14 @@ class MozGridComponent {
10664
11496
  let changed = false;
10665
11497
  for (const col of cols) {
10666
11498
  const coerced = this.coerceAndValidate(col.field, null, updated[dataIndex]);
10667
- if (coerced !== PASTE_SKIP) {
10668
- rowCopy[col.field] = coerced;
10669
- changed = true;
10670
- }
11499
+ if (coerced === PASTE_SKIP)
11500
+ continue;
11501
+ const before = updated[dataIndex][col.field];
11502
+ if (before === coerced)
11503
+ continue;
11504
+ rowCopy[col.field] = coerced;
11505
+ changes.push({ rowIndex: dataIndex, field: col.field, before, after: coerced });
11506
+ changed = true;
10671
11507
  }
10672
11508
  if (changed) {
10673
11509
  updated[dataIndex] = rowCopy;
@@ -10675,6 +11511,7 @@ class MozGridComponent {
10675
11511
  }
10676
11512
  return updated;
10677
11513
  });
11514
+ return changes;
10678
11515
  }
10679
11516
  coerceAndValidate(field, rawValue, row) {
10680
11517
  const def = this.state.columnDefMap().get(field);
@@ -10734,7 +11571,7 @@ class MozGridComponent {
10734
11571
  return value;
10735
11572
  }
10736
11573
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10737
- 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: [
10738
11575
  GridStateManager,
10739
11576
  GridEngine,
10740
11577
  SortEngine,
@@ -10788,15 +11625,24 @@ class MozGridComponent {
10788
11625
  <Download20 icon />
10789
11626
  </moz-icon-button>
10790
11627
  }
10791
- <moz-icon-button
11628
+ <button
11629
+ type="button"
11630
+ moz-button
10792
11631
  id="grid-filter"
10793
11632
  size="s"
10794
11633
  [ghost]="true"
10795
- ariaLabel="Filter"
10796
- (activated)="filterPanelOpen.set(!filterPanelOpen())"
11634
+ [ariaLabel]="'Filters'"
11635
+ class="moz-grid__toolbar-filter-btn"
11636
+ (click)="onFiltersClick()"
10797
11637
  >
10798
11638
  <Filter20 icon />
10799
- </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>
10800
11646
  <moz-icon-button
10801
11647
  id="grid-settings"
10802
11648
  size="s"
@@ -10906,15 +11752,15 @@ class MozGridComponent {
10906
11752
  </button>
10907
11753
  }
10908
11754
  </div>
10909
- } @if (state.activeFilters().length > 0) {
11755
+ } @if (activeFilters().length > 0) {
10910
11756
  <div class="moz-grid__tag-bar">
10911
11757
  <span class="moz-grid__tag-bar-label">FILTERED BY</span>
10912
- @for (filter of state.activeFilters(); track filter.field) {
11758
+ @for (filter of activeFilters(); track filter.id) {
10913
11759
  <moz-tag
10914
11760
  [type]="filter.removable ? 'removable' : 'informative'"
10915
11761
  size="s"
10916
- [id]="'filter-' + filter.field"
10917
- (removeTag)="onRemoveFilter(filter.field)"
11762
+ [id]="'filter-' + filter.id"
11763
+ (removeTag)="onRemoveFilter(filter.id)"
10918
11764
  >{{ filter.label }}</moz-tag
10919
11765
  >
10920
11766
  }
@@ -10976,7 +11822,7 @@ class MozGridComponent {
10976
11822
  />
10977
11823
  </div>
10978
11824
  </div>
10979
- `, 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 });
10980
11826
  }
10981
11827
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridComponent, decorators: [{
10982
11828
  type: Component,
@@ -11053,15 +11899,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11053
11899
  <Download20 icon />
11054
11900
  </moz-icon-button>
11055
11901
  }
11056
- <moz-icon-button
11902
+ <button
11903
+ type="button"
11904
+ moz-button
11057
11905
  id="grid-filter"
11058
11906
  size="s"
11059
11907
  [ghost]="true"
11060
- ariaLabel="Filter"
11061
- (activated)="filterPanelOpen.set(!filterPanelOpen())"
11908
+ [ariaLabel]="'Filters'"
11909
+ class="moz-grid__toolbar-filter-btn"
11910
+ (click)="onFiltersClick()"
11062
11911
  >
11063
11912
  <Filter20 icon />
11064
- </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>
11065
11920
  <moz-icon-button
11066
11921
  id="grid-settings"
11067
11922
  size="s"
@@ -11171,15 +12026,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11171
12026
  </button>
11172
12027
  }
11173
12028
  </div>
11174
- } @if (state.activeFilters().length > 0) {
12029
+ } @if (activeFilters().length > 0) {
11175
12030
  <div class="moz-grid__tag-bar">
11176
12031
  <span class="moz-grid__tag-bar-label">FILTERED BY</span>
11177
- @for (filter of state.activeFilters(); track filter.field) {
12032
+ @for (filter of activeFilters(); track filter.id) {
11178
12033
  <moz-tag
11179
12034
  [type]="filter.removable ? 'removable' : 'informative'"
11180
12035
  size="s"
11181
- [id]="'filter-' + filter.field"
11182
- (removeTag)="onRemoveFilter(filter.field)"
12036
+ [id]="'filter-' + filter.id"
12037
+ (removeTag)="onRemoveFilter(filter.id)"
11183
12038
  >{{ filter.label }}</moz-tag
11184
12039
  >
11185
12040
  }
@@ -11241,9 +12096,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
11241
12096
  />
11242
12097
  </div>
11243
12098
  </div>
11244
- `, 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"] }]
11245
- }], 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"] }] } });
11246
- const PASTE_SKIP = Symbol('PASTE_SKIP');
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 }] }] } });
11247
12101
 
11248
12102
  const DEFAULT_GRID_OPTIONS = {
11249
12103
  mode: 'client',