@vaadin/grid 24.2.0-dev.f254716fe → 24.3.0-alpha2

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.
Files changed (40) hide show
  1. package/package.json +11 -11
  2. package/src/vaadin-grid-array-data-provider-mixin.js +1 -1
  3. package/src/vaadin-grid-column-group-mixin.d.ts +20 -0
  4. package/src/vaadin-grid-column-group-mixin.js +369 -0
  5. package/src/vaadin-grid-column-group.d.ts +4 -14
  6. package/src/vaadin-grid-column-group.js +8 -356
  7. package/src/vaadin-grid-column-mixin.d.ts +156 -0
  8. package/src/vaadin-grid-column-mixin.js +887 -0
  9. package/src/vaadin-grid-column.d.ts +11 -138
  10. package/src/vaadin-grid-column.js +6 -876
  11. package/src/vaadin-grid-data-provider-mixin.d.ts +6 -5
  12. package/src/vaadin-grid-data-provider-mixin.js +51 -20
  13. package/src/vaadin-grid-drag-and-drop-mixin.js +1 -1
  14. package/src/vaadin-grid-dynamic-columns-mixin.js +1 -1
  15. package/src/vaadin-grid-filter-column.js +5 -1
  16. package/src/vaadin-grid-filter-element-mixin.d.ts +34 -0
  17. package/src/vaadin-grid-filter-element-mixin.js +99 -0
  18. package/src/vaadin-grid-filter.d.ts +4 -21
  19. package/src/vaadin-grid-filter.js +8 -85
  20. package/src/vaadin-grid-keyboard-navigation-mixin.js +24 -4
  21. package/src/vaadin-grid-mixin.d.ts +218 -0
  22. package/src/vaadin-grid-mixin.js +1022 -0
  23. package/src/vaadin-grid-scroll-mixin.js +1 -1
  24. package/src/vaadin-grid-selection-column-base-mixin.d.ts +6 -0
  25. package/src/vaadin-grid-selection-column-base-mixin.js +151 -0
  26. package/src/vaadin-grid-selection-column.js +4 -1
  27. package/src/vaadin-grid-sort-column.js +5 -1
  28. package/src/vaadin-grid-sorter-mixin.d.ts +44 -0
  29. package/src/vaadin-grid-sorter-mixin.js +198 -0
  30. package/src/vaadin-grid-sorter.d.ts +3 -32
  31. package/src/vaadin-grid-sorter.js +8 -182
  32. package/src/vaadin-grid-tree-column-mixin.d.ts +19 -0
  33. package/src/vaadin-grid-tree-column-mixin.js +92 -0
  34. package/src/vaadin-grid-tree-column.d.ts +9 -7
  35. package/src/vaadin-grid-tree-column.js +7 -82
  36. package/src/vaadin-grid-tree-toggle.js +3 -1
  37. package/src/vaadin-grid.d.ts +5 -190
  38. package/src/vaadin-grid.js +7 -1018
  39. package/web-types.json +2311 -0
  40. package/web-types.lit.json +1007 -0
@@ -6,41 +6,11 @@
6
6
  import './vaadin-grid-column.js';
7
7
  import './vaadin-grid-styles.js';
8
8
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
9
- import { isElementHidden } from '@vaadin/a11y-base/src/focus-utils.js';
10
- import { TabindexMixin } from '@vaadin/a11y-base/src/tabindex-mixin.js';
11
- import { animationFrame, microTask } from '@vaadin/component-base/src/async.js';
12
- import { isAndroid, isChrome, isFirefox, isIOS, isSafari, isTouch } from '@vaadin/component-base/src/browser-utils.js';
13
9
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
14
- import { Debouncer } from '@vaadin/component-base/src/debounce.js';
15
- import { getClosestElement } from '@vaadin/component-base/src/dom-utils.js';
10
+ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
16
11
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
17
- import { processTemplates } from '@vaadin/component-base/src/templates.js';
18
- import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
19
- import { Virtualizer } from '@vaadin/component-base/src/virtualizer.js';
20
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
21
- import { A11yMixin } from './vaadin-grid-a11y-mixin.js';
22
- import { ActiveItemMixin } from './vaadin-grid-active-item-mixin.js';
23
- import { ArrayDataProviderMixin } from './vaadin-grid-array-data-provider-mixin.js';
24
- import { ColumnReorderingMixin } from './vaadin-grid-column-reordering-mixin.js';
25
- import { ColumnResizingMixin } from './vaadin-grid-column-resizing-mixin.js';
26
- import { DataProviderMixin } from './vaadin-grid-data-provider-mixin.js';
27
- import { DragAndDropMixin } from './vaadin-grid-drag-and-drop-mixin.js';
28
- import { DynamicColumnsMixin } from './vaadin-grid-dynamic-columns-mixin.js';
29
- import { EventContextMixin } from './vaadin-grid-event-context-mixin.js';
30
- import { FilterMixin } from './vaadin-grid-filter-mixin.js';
31
- import {
32
- getBodyRowCells,
33
- iterateChildren,
34
- iterateRowCells,
35
- updateBooleanRowStates,
36
- updateCellsPart,
37
- } from './vaadin-grid-helpers.js';
38
- import { KeyboardNavigationMixin } from './vaadin-grid-keyboard-navigation-mixin.js';
39
- import { RowDetailsMixin } from './vaadin-grid-row-details-mixin.js';
40
- import { ScrollMixin } from './vaadin-grid-scroll-mixin.js';
41
- import { SelectionMixin } from './vaadin-grid-selection-mixin.js';
42
- import { SortMixin } from './vaadin-grid-sort-mixin.js';
43
- import { StylingMixin } from './vaadin-grid-styling-mixin.js';
13
+ import { GridMixin } from './vaadin-grid-mixin.js';
44
14
 
45
15
  /**
46
16
  * `<vaadin-grid>` is a free, high quality data grid / data table Web Component. The content of the
@@ -278,59 +248,13 @@ import { StylingMixin } from './vaadin-grid-styling-mixin.js';
278
248
  * @fires {CustomEvent} selected-items-changed - Fired when the `selectedItems` property changes.
279
249
  * @fires {CustomEvent} size-changed - Fired when the `size` property changes.
280
250
  *
251
+ * @customElement
281
252
  * @extends HTMLElement
282
- * @mixes ElementMixin
253
+ * @mixes GridMixin
283
254
  * @mixes ThemableMixin
284
- * @mixes A11yMixin
285
- * @mixes ActiveItemMixin
286
- * @mixes ArrayDataProviderMixin
287
- * @mixes ColumnResizingMixin
288
- * @mixes DataProviderMixin
289
- * @mixes DynamicColumnsMixin
290
- * @mixes FilterMixin
291
- * @mixes RowDetailsMixin
292
- * @mixes ScrollMixin
293
- * @mixes SelectionMixin
294
- * @mixes SortMixin
295
- * @mixes KeyboardNavigationMixin
296
- * @mixes ColumnReorderingMixin
297
- * @mixes EventContextMixin
298
- * @mixes StylingMixin
299
- * @mixes DragAndDropMixin
255
+ * @mixes ControllerMixin
300
256
  */
301
- class Grid extends ElementMixin(
302
- ThemableMixin(
303
- ArrayDataProviderMixin(
304
- DataProviderMixin(
305
- DynamicColumnsMixin(
306
- ActiveItemMixin(
307
- ScrollMixin(
308
- SelectionMixin(
309
- SortMixin(
310
- RowDetailsMixin(
311
- KeyboardNavigationMixin(
312
- A11yMixin(
313
- FilterMixin(
314
- ColumnReorderingMixin(
315
- ColumnResizingMixin(
316
- ControllerMixin(
317
- EventContextMixin(DragAndDropMixin(StylingMixin(TabindexMixin(PolymerElement)))),
318
- ),
319
- ),
320
- ),
321
- ),
322
- ),
323
- ),
324
- ),
325
- ),
326
- ),
327
- ),
328
- ),
329
- ),
330
- ),
331
- ),
332
- ),
333
- ) {
257
+ class Grid extends GridMixin(ElementMixin(ThemableMixin(ControllerMixin(PolymerElement)))) {
334
258
  static get template() {
335
259
  return html`
336
260
  <div
@@ -359,943 +283,8 @@ class Grid extends ElementMixin(
359
283
  static get is() {
360
284
  return 'vaadin-grid';
361
285
  }
362
-
363
- static get observers() {
364
- return [
365
- '_columnTreeChanged(_columnTree, _columnTree.*)',
366
- '_effectiveSizeChanged(_effectiveSize, __virtualizer, _hasData, _columnTree)',
367
- ];
368
- }
369
-
370
- static get properties() {
371
- return {
372
- /** @private */
373
- _safari: {
374
- type: Boolean,
375
- value: isSafari,
376
- },
377
-
378
- /** @private */
379
- _ios: {
380
- type: Boolean,
381
- value: isIOS,
382
- },
383
-
384
- /** @private */
385
- _firefox: {
386
- type: Boolean,
387
- value: isFirefox,
388
- },
389
-
390
- /** @private */
391
- _android: {
392
- type: Boolean,
393
- value: isAndroid,
394
- },
395
-
396
- /** @private */
397
- _touchDevice: {
398
- type: Boolean,
399
- value: isTouch,
400
- },
401
-
402
- /**
403
- * If true, the grid's height is defined by its rows.
404
- *
405
- * Effectively, this disables the grid's virtual scrolling so that all the rows are rendered in the DOM at once.
406
- * If the grid has a large number of items, using the feature is discouraged to avoid performance issues.
407
- * @attr {boolean} all-rows-visible
408
- * @type {boolean}
409
- */
410
- allRowsVisible: {
411
- type: Boolean,
412
- value: false,
413
- reflectToAttribute: true,
414
- },
415
-
416
- /** @private */
417
- __pendingRecalculateColumnWidths: {
418
- type: Boolean,
419
- value: true,
420
- },
421
-
422
- /** @private */
423
- isAttached: {
424
- value: false,
425
- },
426
-
427
- /**
428
- * An internal property that is mainly used by `vaadin-template-renderer`
429
- * to identify grid elements.
430
- *
431
- * @private
432
- */
433
- __gridElement: {
434
- type: Boolean,
435
- value: true,
436
- },
437
- };
438
- }
439
-
440
- constructor() {
441
- super();
442
- this.addEventListener('animationend', this._onAnimationEnd);
443
- }
444
-
445
- /** @private */
446
- get _firstVisibleIndex() {
447
- const firstVisibleItem = this.__getFirstVisibleItem();
448
- return firstVisibleItem ? firstVisibleItem.index : undefined;
449
- }
450
-
451
- /** @private */
452
- get _lastVisibleIndex() {
453
- const lastVisibleItem = this.__getLastVisibleItem();
454
- return lastVisibleItem ? lastVisibleItem.index : undefined;
455
- }
456
-
457
- /** @protected */
458
- connectedCallback() {
459
- super.connectedCallback();
460
- this.isAttached = true;
461
- this.recalculateColumnWidths();
462
- }
463
-
464
- /** @protected */
465
- disconnectedCallback() {
466
- super.disconnectedCallback();
467
- this.isAttached = false;
468
- this._hideTooltip(true);
469
- }
470
-
471
- /** @private */
472
- __getFirstVisibleItem() {
473
- return this._getRenderedRows().find((row) => this._isInViewport(row));
474
- }
475
-
476
- /** @private */
477
- __getLastVisibleItem() {
478
- return this._getRenderedRows()
479
- .reverse()
480
- .find((row) => this._isInViewport(row));
481
- }
482
-
483
- /** @private */
484
- _isInViewport(item) {
485
- const scrollTargetRect = this.$.table.getBoundingClientRect();
486
- const itemRect = item.getBoundingClientRect();
487
- const headerHeight = this.$.header.getBoundingClientRect().height;
488
- const footerHeight = this.$.footer.getBoundingClientRect().height;
489
- return (
490
- itemRect.bottom > scrollTargetRect.top + headerHeight && itemRect.top < scrollTargetRect.bottom - footerHeight
491
- );
492
- }
493
-
494
- /** @private */
495
- _getRenderedRows() {
496
- return Array.from(this.$.items.children)
497
- .filter((item) => !item.hidden)
498
- .sort((a, b) => a.index - b.index);
499
- }
500
-
501
- /** @protected */
502
- _getRowContainingNode(node) {
503
- const content = getClosestElement('vaadin-grid-cell-content', node);
504
- if (!content) {
505
- return;
506
- }
507
-
508
- const cell = content.assignedSlot.parentElement;
509
- return cell.parentElement;
510
- }
511
-
512
- /** @protected */
513
- _isItemAssignedToRow(item, row) {
514
- const model = this.__getRowModel(row);
515
- return this.getItemId(item) === this.getItemId(model.item);
516
- }
517
-
518
- /** @protected */
519
- ready() {
520
- super.ready();
521
-
522
- this.__virtualizer = new Virtualizer({
523
- createElements: this._createScrollerRows.bind(this),
524
- updateElement: this._updateScrollerItem.bind(this),
525
- scrollContainer: this.$.items,
526
- scrollTarget: this.$.table,
527
- reorderElements: true,
528
- });
529
-
530
- new ResizeObserver(() =>
531
- setTimeout(() => {
532
- this.__updateFooterPositioning();
533
- this.__updateColumnsBodyContentHidden();
534
- this.__tryToRecalculateColumnWidthsIfPending();
535
- }),
536
- ).observe(this.$.table);
537
-
538
- processTemplates(this);
539
-
540
- this._tooltipController = new TooltipController(this);
541
- this.addController(this._tooltipController);
542
- this._tooltipController.setManual(true);
543
- }
544
-
545
- /** @private */
546
- __getBodyCellCoordinates(cell) {
547
- if (this.$.items.contains(cell) && cell.localName === 'td') {
548
- return {
549
- item: cell.parentElement._item,
550
- column: cell._column,
551
- };
552
- }
553
- }
554
-
555
- /** @private */
556
- __focusBodyCell({ item, column }) {
557
- const row = this._getRenderedRows().find((row) => row._item === item);
558
- const cell = row && [...row.children].find((cell) => cell._column === column);
559
- if (cell) {
560
- cell.focus();
561
- }
562
- }
563
-
564
- /** @protected */
565
- _focusFirstVisibleRow() {
566
- const row = this.__getFirstVisibleItem();
567
- this.__rowFocusMode = true;
568
- row.focus();
569
- }
570
-
571
- /** @private */
572
- _effectiveSizeChanged(effectiveSize, virtualizer, hasData, columnTree) {
573
- if (virtualizer && hasData && columnTree) {
574
- // Changing the virtualizer size may result in the row with focus getting hidden
575
- const cell = this.shadowRoot.activeElement;
576
- const cellCoordinates = this.__getBodyCellCoordinates(cell);
577
-
578
- const previousSize = virtualizer.size || 0;
579
- virtualizer.size = effectiveSize;
580
-
581
- // Request an update for the previous last row to have the "last" state removed
582
- virtualizer.update(previousSize - 1, previousSize - 1);
583
- if (effectiveSize < previousSize) {
584
- // Size was decreased, so the new last row requires an explicit update
585
- virtualizer.update(effectiveSize - 1, effectiveSize - 1);
586
- }
587
-
588
- // If the focused cell's parent row got hidden by the size change, focus the corresponding new cell
589
- if (cellCoordinates && cell.parentElement.hidden) {
590
- this.__focusBodyCell(cellCoordinates);
591
- }
592
-
593
- // Make sure the body has a tabbable element
594
- this._resetKeyboardNavigation();
595
- }
596
- }
597
-
598
- /** @private */
599
- __hasRowsWithClientHeight() {
600
- return !!Array.from(this.$.items.children).filter((row) => row.clientHeight).length;
601
- }
602
-
603
- /** @private */
604
- __getIntrinsicWidth(col) {
605
- if (!this.__intrinsicWidthCache.has(col)) {
606
- this.__calculateAndCacheIntrinsicWidths([col]);
607
- }
608
- return this.__intrinsicWidthCache.get(col);
609
- }
610
-
611
- /** @private */
612
- __getDistributedWidth(col, innerColumn) {
613
- if (col == null || col === this) {
614
- return 0;
615
- }
616
-
617
- const columnWidth = Math.max(this.__getIntrinsicWidth(col), this.__getDistributedWidth(col.parentElement, col));
618
-
619
- // We're processing a regular grid-column and not a grid-column-group
620
- if (!innerColumn) {
621
- return columnWidth;
622
- }
623
-
624
- // At the end, the width of each vaadin-grid-column-group is determined by the sum of the width of its children.
625
- // Here we determine how much space the vaadin-grid-column-group actually needs to render properly and then we distribute that space
626
- // to its children, so when we actually do the summation it will be rendered properly.
627
- // Check out vaadin-grid-column-group:_updateFlexAndWidth
628
- const columnGroup = col;
629
- const columnGroupWidth = columnWidth;
630
- const sumOfWidthOfAllChildColumns = columnGroup._visibleChildColumns
631
- .map((col) => this.__getIntrinsicWidth(col))
632
- .reduce((sum, curr) => sum + curr, 0);
633
-
634
- const extraNecessarySpaceForGridColumnGroup = Math.max(0, columnGroupWidth - sumOfWidthOfAllChildColumns);
635
-
636
- // The distribution of the extra necessary space is done according to the intrinsic width of each child column.
637
- // Lets say we need 100 pixels of extra space for the grid-column-group to render properly
638
- // it has two grid-column children, |100px|300px| in total 400px
639
- // the first column gets 25px of the additional space (100/400)*100 = 25
640
- // the second column gets the 75px of the additional space (300/400)*100 = 75
641
- const proportionOfExtraSpace = this.__getIntrinsicWidth(innerColumn) / sumOfWidthOfAllChildColumns;
642
- const shareOfInnerColumnFromNecessaryExtraSpace = proportionOfExtraSpace * extraNecessarySpaceForGridColumnGroup;
643
-
644
- return this.__getIntrinsicWidth(innerColumn) + shareOfInnerColumnFromNecessaryExtraSpace;
645
- }
646
-
647
- /**
648
- * @param {!Array<!GridColumn>} cols the columns to auto size based on their content width
649
- * @private
650
- */
651
- _recalculateColumnWidths(cols) {
652
- // Flush to make sure DOM is up-to-date when measuring the column widths
653
- this.__virtualizer.flush();
654
- [...this.$.header.children, ...this.$.footer.children].forEach((row) => {
655
- if (row.__debounceUpdateHeaderFooterRowVisibility) {
656
- row.__debounceUpdateHeaderFooterRowVisibility.flush();
657
- }
658
- });
659
-
660
- // Flush to account for any changes to the visibility of the columns
661
- if (this._debouncerHiddenChanged) {
662
- this._debouncerHiddenChanged.flush();
663
- }
664
-
665
- this.__intrinsicWidthCache = new Map();
666
- // Cache the viewport rows to avoid unnecessary reflows while measuring the column widths
667
- const fvi = this._firstVisibleIndex;
668
- const lvi = this._lastVisibleIndex;
669
- this.__viewportRowsCache = this._getRenderedRows().filter((row) => row.index >= fvi && row.index <= lvi);
670
-
671
- // Pre-cache the intrinsic width of each column
672
- this.__calculateAndCacheIntrinsicWidths(cols);
673
-
674
- cols.forEach((col) => {
675
- col.width = `${this.__getDistributedWidth(col)}px`;
676
- });
677
- }
678
-
679
- /**
680
- * Toggles the cell content for the given column to use or not use auto width.
681
- *
682
- * While content for all the column cells uses auto width (instead of the default 100%),
683
- * their offsetWidth can be used to calculate the collective intrinsic width of the column.
684
- *
685
- * @private
686
- */
687
- __setVisibleCellContentAutoWidth(col, autoWidth) {
688
- col._allCells
689
- .filter((cell) => {
690
- if (this.$.items.contains(cell)) {
691
- return this.__viewportRowsCache.includes(cell.parentElement);
692
- }
693
- return true;
694
- })
695
- .forEach((cell) => {
696
- cell.__measuringAutoWidth = autoWidth;
697
- cell._content.style.width = autoWidth ? 'auto' : '';
698
- cell._content.style.position = autoWidth ? 'absolute' : '';
699
- });
700
- }
701
-
702
- /**
703
- * Returns the maximum intrinsic width of the cell content in the given column.
704
- * Only cells which are marked for measuring auto width are considered.
705
- *
706
- * @private
707
- */
708
- __getAutoWidthCellsMaxWidth(col) {
709
- // Note: _allCells only contains cells which are currently rendered in DOM
710
- return col._allCells.reduce((width, cell) => {
711
- // Add 1px buffer to the offset width to avoid too narrow columns (sub-pixel rendering)
712
- return cell.__measuringAutoWidth ? Math.max(width, cell._content.offsetWidth + 1) : width;
713
- }, 0);
714
- }
715
-
716
- /**
717
- * Calculates and caches the intrinsic width of each given column.
718
- *
719
- * @private
720
- */
721
- __calculateAndCacheIntrinsicWidths(cols) {
722
- // Make all the columns use auto width at once before measuring to
723
- // avoid reflows in between the measurements
724
- cols.forEach((col) => this.__setVisibleCellContentAutoWidth(col, true));
725
- // Measure and cache
726
- cols.forEach((col) => {
727
- const width = this.__getAutoWidthCellsMaxWidth(col);
728
- this.__intrinsicWidthCache.set(col, width);
729
- });
730
- // Reset the columns to use 100% width
731
- cols.forEach((col) => this.__setVisibleCellContentAutoWidth(col, false));
732
- }
733
-
734
- /**
735
- * Updates the `width` of all columns which have `autoWidth` set to `true`.
736
- */
737
- recalculateColumnWidths() {
738
- if (!this._columnTree) {
739
- return; // No columns
740
- }
741
- if (isElementHidden(this) || this._dataProviderController.isLoading()) {
742
- this.__pendingRecalculateColumnWidths = true;
743
- return;
744
- }
745
- const cols = this._getColumns().filter((col) => !col.hidden && col.autoWidth);
746
- this._recalculateColumnWidths(cols);
747
- }
748
-
749
- /** @private */
750
- __tryToRecalculateColumnWidthsIfPending() {
751
- if (
752
- this.__pendingRecalculateColumnWidths &&
753
- !isElementHidden(this) &&
754
- !this._dataProviderController.isLoading() &&
755
- this.__hasRowsWithClientHeight()
756
- ) {
757
- this.__pendingRecalculateColumnWidths = false;
758
- this.recalculateColumnWidths();
759
- }
760
- }
761
-
762
- /**
763
- * @protected
764
- * @override
765
- */
766
- _onDataProviderPageLoaded() {
767
- super._onDataProviderPageLoaded();
768
- this.__tryToRecalculateColumnWidthsIfPending();
769
- }
770
-
771
- /** @private */
772
- _createScrollerRows(count) {
773
- const rows = [];
774
- for (let i = 0; i < count; i++) {
775
- const row = document.createElement('tr');
776
- row.setAttribute('part', 'row');
777
- row.setAttribute('role', 'row');
778
- row.setAttribute('tabindex', '-1');
779
- if (this._columnTree) {
780
- this._updateRow(row, this._columnTree[this._columnTree.length - 1], 'body', false, true);
781
- }
782
- rows.push(row);
783
- }
784
-
785
- if (this._columnTree) {
786
- this._columnTree[this._columnTree.length - 1].forEach(
787
- (c) => c.isConnected && c.notifyPath && c.notifyPath('_cells.*', c._cells),
788
- );
789
- }
790
-
791
- this.__afterCreateScrollerRowsDebouncer = Debouncer.debounce(
792
- this.__afterCreateScrollerRowsDebouncer,
793
- animationFrame,
794
- () => {
795
- this._afterScroll();
796
- this.__tryToRecalculateColumnWidthsIfPending();
797
- },
798
- );
799
- return rows;
800
- }
801
-
802
- /** @private */
803
- _createCell(tagName, column) {
804
- const contentId = (this._contentIndex = this._contentIndex + 1 || 0);
805
- const slotName = `vaadin-grid-cell-content-${contentId}`;
806
-
807
- const cellContent = document.createElement('vaadin-grid-cell-content');
808
- cellContent.setAttribute('slot', slotName);
809
-
810
- const cell = document.createElement(tagName);
811
- cell.id = slotName.replace('-content-', '-');
812
- cell.setAttribute('role', tagName === 'td' ? 'gridcell' : 'columnheader');
813
-
814
- // For now we only support tooltip on desktop
815
- if (!isAndroid && !isIOS) {
816
- cell.addEventListener('mouseenter', (event) => {
817
- if (!this.$.scroller.hasAttribute('scrolling')) {
818
- this._showTooltip(event);
819
- }
820
- });
821
-
822
- cell.addEventListener('mouseleave', () => {
823
- this._hideTooltip();
824
- });
825
-
826
- cell.addEventListener('mousedown', () => {
827
- this._hideTooltip(true);
828
- });
829
- }
830
-
831
- const slot = document.createElement('slot');
832
- slot.setAttribute('name', slotName);
833
-
834
- if (column && column._focusButtonMode) {
835
- const div = document.createElement('div');
836
- div.setAttribute('role', 'button');
837
- div.setAttribute('tabindex', '-1');
838
- cell.appendChild(div);
839
-
840
- // Patch `focus()` to use the button
841
- cell._focusButton = div;
842
- cell.focus = function () {
843
- cell._focusButton.focus();
844
- };
845
-
846
- div.appendChild(slot);
847
- } else {
848
- cell.setAttribute('tabindex', '-1');
849
- cell.appendChild(slot);
850
- }
851
-
852
- cell._content = cellContent;
853
-
854
- // With native Shadow DOM, mousedown on slotted element does not focus
855
- // focusable slot wrapper, that is why cells are not focused with
856
- // mousedown. Workaround: listen for mousedown and focus manually.
857
- cellContent.addEventListener('mousedown', () => {
858
- if (isChrome) {
859
- // Chrome bug: focusing before mouseup prevents text selection, see http://crbug.com/771903
860
- const mouseUpListener = (event) => {
861
- // If focus is on element within the cell content - respect it, do not change
862
- const contentContainsFocusedElement = cellContent.contains(this.getRootNode().activeElement);
863
- // Only focus if mouse is released on cell content itself
864
- const mouseUpWithinCell = event.composedPath().includes(cellContent);
865
- if (!contentContainsFocusedElement && mouseUpWithinCell) {
866
- cell.focus();
867
- }
868
- document.removeEventListener('mouseup', mouseUpListener, true);
869
- };
870
- document.addEventListener('mouseup', mouseUpListener, true);
871
- } else {
872
- // Focus on mouseup, on the other hand, removes selection on Safari.
873
- // Watch out sync focus removal issue, only async focus works here.
874
- setTimeout(() => {
875
- if (!cellContent.contains(this.getRootNode().activeElement)) {
876
- cell.focus();
877
- }
878
- });
879
- }
880
- });
881
-
882
- return cell;
883
- }
884
-
885
- /**
886
- * @param {!HTMLTableRowElement} row
887
- * @param {!Array<!GridColumn>} columns
888
- * @param {?string} section
889
- * @param {boolean} isColumnRow
890
- * @param {boolean} noNotify
891
- * @protected
892
- */
893
- _updateRow(row, columns, section = 'body', isColumnRow = false, noNotify = false) {
894
- const contentsFragment = document.createDocumentFragment();
895
-
896
- iterateRowCells(row, (cell) => {
897
- cell._vacant = true;
898
- });
899
- row.innerHTML = '';
900
- if (section === 'body') {
901
- // Clear the cached cell references
902
- row.__cells = [];
903
- row.__detailsCell = null;
904
- }
905
-
906
- columns
907
- .filter((column) => !column.hidden)
908
- .forEach((column, index, cols) => {
909
- let cell;
910
-
911
- if (section === 'body') {
912
- // Body
913
- if (!column._cells) {
914
- column._cells = [];
915
- }
916
- cell = column._cells.find((cell) => cell._vacant);
917
- if (!cell) {
918
- cell = this._createCell('td', column);
919
- column._cells.push(cell);
920
- }
921
- cell.setAttribute('part', 'cell body-cell');
922
- cell.__parentRow = row;
923
- // Cache the cell reference
924
- row.__cells.push(cell);
925
- if (!column._bodyContentHidden) {
926
- row.appendChild(cell);
927
- }
928
-
929
- if (row === this.$.sizer) {
930
- column._sizerCell = cell;
931
- }
932
-
933
- if (index === cols.length - 1 && this.rowDetailsRenderer) {
934
- // Add details cell as last cell to body rows
935
- if (!this._detailsCells) {
936
- this._detailsCells = [];
937
- }
938
- const detailsCell = this._detailsCells.find((cell) => cell._vacant) || this._createCell('td');
939
- if (this._detailsCells.indexOf(detailsCell) === -1) {
940
- this._detailsCells.push(detailsCell);
941
- }
942
- if (!detailsCell._content.parentElement) {
943
- contentsFragment.appendChild(detailsCell._content);
944
- }
945
- this._configureDetailsCell(detailsCell);
946
- row.appendChild(detailsCell);
947
- // Cache the details cell reference
948
- row.__detailsCell = detailsCell;
949
- this._a11ySetRowDetailsCell(row, detailsCell);
950
- detailsCell._vacant = false;
951
- }
952
-
953
- if (column.notifyPath && !noNotify) {
954
- column.notifyPath('_cells.*', column._cells);
955
- }
956
- } else {
957
- // Header & footer
958
- const tagName = section === 'header' ? 'th' : 'td';
959
- if (isColumnRow || column.localName === 'vaadin-grid-column-group') {
960
- cell = column[`_${section}Cell`] || this._createCell(tagName);
961
- cell._column = column;
962
- row.appendChild(cell);
963
- column[`_${section}Cell`] = cell;
964
- } else {
965
- if (!column._emptyCells) {
966
- column._emptyCells = [];
967
- }
968
- cell = column._emptyCells.find((cell) => cell._vacant) || this._createCell(tagName);
969
- cell._column = column;
970
- row.appendChild(cell);
971
- if (column._emptyCells.indexOf(cell) === -1) {
972
- column._emptyCells.push(cell);
973
- }
974
- }
975
- cell.setAttribute('part', `cell ${section}-cell`);
976
- }
977
-
978
- if (!cell._content.parentElement) {
979
- contentsFragment.appendChild(cell._content);
980
- }
981
- cell._vacant = false;
982
- cell._column = column;
983
- });
984
-
985
- if (section !== 'body') {
986
- this.__debounceUpdateHeaderFooterRowVisibility(row);
987
- }
988
-
989
- // Might be empty if only cache was used
990
- this.appendChild(contentsFragment);
991
-
992
- this._frozenCellsChanged();
993
- this._updateFirstAndLastColumnForRow(row);
994
- }
995
-
996
- /**
997
- * @param {HTMLTableRowElement} row
998
- * @protected
999
- */
1000
- __debounceUpdateHeaderFooterRowVisibility(row) {
1001
- row.__debounceUpdateHeaderFooterRowVisibility = Debouncer.debounce(
1002
- row.__debounceUpdateHeaderFooterRowVisibility,
1003
- microTask,
1004
- () => this.__updateHeaderFooterRowVisibility(row),
1005
- );
1006
- }
1007
-
1008
- /**
1009
- * @param {HTMLTableRowElement} row
1010
- * @protected
1011
- */
1012
- __updateHeaderFooterRowVisibility(row) {
1013
- if (!row) {
1014
- return;
1015
- }
1016
-
1017
- const visibleRowCells = Array.from(row.children).filter((cell) => {
1018
- const column = cell._column;
1019
- if (column._emptyCells && column._emptyCells.indexOf(cell) > -1) {
1020
- // The cell is an "empty cell" -> doesn't block hiding the row
1021
- return false;
1022
- }
1023
- if (row.parentElement === this.$.header) {
1024
- if (column.headerRenderer) {
1025
- // The cell is the header cell of a column that has a header renderer
1026
- // -> row should be visible
1027
- return true;
1028
- }
1029
- if (column.header === null) {
1030
- // The column header is explicilty set to null -> doesn't block hiding the row
1031
- return false;
1032
- }
1033
- if (column.path || column.header !== undefined) {
1034
- // The column has an explicit non-null header or a path that generates a header
1035
- // -> row should be visible
1036
- return true;
1037
- }
1038
- } else if (column.footerRenderer) {
1039
- // The cell is the footer cell of a column that has a footer renderer
1040
- // -> row should be visible
1041
- return true;
1042
- }
1043
- return false;
1044
- });
1045
-
1046
- if (row.hidden !== !visibleRowCells.length) {
1047
- row.hidden = !visibleRowCells.length;
1048
- }
1049
-
1050
- // Make sure the section has a tabbable element
1051
- this._resetKeyboardNavigation();
1052
- }
1053
-
1054
- /** @private */
1055
- _updateScrollerItem(row, index) {
1056
- this._preventScrollerRotatingCellFocus(row, index);
1057
-
1058
- if (!this._columnTree) {
1059
- return;
1060
- }
1061
-
1062
- this._updateRowOrderParts(row, index);
1063
-
1064
- this._a11yUpdateRowRowindex(row, index);
1065
- this._getItem(index, row);
1066
- }
1067
-
1068
- /** @private */
1069
- _columnTreeChanged(columnTree) {
1070
- this._renderColumnTree(columnTree);
1071
- this.recalculateColumnWidths();
1072
- this.__updateColumnsBodyContentHidden();
1073
- }
1074
-
1075
- /** @private */
1076
- _updateRowOrderParts(row, index = row.index) {
1077
- updateBooleanRowStates(row, {
1078
- first: index === 0,
1079
- last: index === this._effectiveSize - 1,
1080
- odd: index % 2 !== 0,
1081
- even: index % 2 === 0,
1082
- });
1083
- }
1084
-
1085
- /** @private */
1086
- _updateRowStateParts(row, { expanded, selected, detailsOpened }) {
1087
- updateBooleanRowStates(row, {
1088
- expanded,
1089
- selected,
1090
- 'details-opened': detailsOpened,
1091
- });
1092
- }
1093
-
1094
- /**
1095
- * @param {!Array<!GridColumn>} columnTree
1096
- * @protected
1097
- */
1098
- _renderColumnTree(columnTree) {
1099
- iterateChildren(this.$.items, (row) => {
1100
- this._updateRow(row, columnTree[columnTree.length - 1], 'body', false, true);
1101
-
1102
- const model = this.__getRowModel(row);
1103
- this._updateRowOrderParts(row);
1104
- this._updateRowStateParts(row, model);
1105
- this._filterDragAndDrop(row, model);
1106
- });
1107
-
1108
- while (this.$.header.children.length < columnTree.length) {
1109
- const headerRow = document.createElement('tr');
1110
- headerRow.setAttribute('part', 'row');
1111
- headerRow.setAttribute('role', 'row');
1112
- headerRow.setAttribute('tabindex', '-1');
1113
- this.$.header.appendChild(headerRow);
1114
-
1115
- const footerRow = document.createElement('tr');
1116
- footerRow.setAttribute('part', 'row');
1117
- footerRow.setAttribute('role', 'row');
1118
- footerRow.setAttribute('tabindex', '-1');
1119
- this.$.footer.appendChild(footerRow);
1120
- }
1121
- while (this.$.header.children.length > columnTree.length) {
1122
- this.$.header.removeChild(this.$.header.firstElementChild);
1123
- this.$.footer.removeChild(this.$.footer.firstElementChild);
1124
- }
1125
-
1126
- iterateChildren(this.$.header, (headerRow, index, rows) => {
1127
- this._updateRow(headerRow, columnTree[index], 'header', index === columnTree.length - 1);
1128
-
1129
- const cells = getBodyRowCells(headerRow);
1130
- updateCellsPart(cells, 'first-header-row-cell', index === 0);
1131
- updateCellsPart(cells, 'last-header-row-cell', index === rows.length - 1);
1132
- });
1133
-
1134
- iterateChildren(this.$.footer, (footerRow, index, rows) => {
1135
- this._updateRow(footerRow, columnTree[columnTree.length - 1 - index], 'footer', index === 0);
1136
-
1137
- const cells = getBodyRowCells(footerRow);
1138
- updateCellsPart(cells, 'first-footer-row-cell', index === 0);
1139
- updateCellsPart(cells, 'last-footer-row-cell', index === rows.length - 1);
1140
- });
1141
-
1142
- // Sizer rows
1143
- this._updateRow(this.$.sizer, columnTree[columnTree.length - 1]);
1144
-
1145
- this._resizeHandler();
1146
- this._frozenCellsChanged();
1147
- this._updateFirstAndLastColumn();
1148
- this._resetKeyboardNavigation();
1149
- this._a11yUpdateHeaderRows();
1150
- this._a11yUpdateFooterRows();
1151
- this.__updateFooterPositioning();
1152
- this.generateCellClassNames();
1153
- this.generateCellPartNames();
1154
- }
1155
-
1156
- /** @private */
1157
- __updateFooterPositioning() {
1158
- // TODO: fixed in Firefox 99, remove when we can drop Firefox ESR 91 support
1159
- if (this._firefox && parseFloat(navigator.userAgent.match(/Firefox\/(\d{2,3}.\d)/u)[1]) < 99) {
1160
- // Sticky (or translated) footer in a flexbox host doesn't get included in
1161
- // the scroll height calculation on FF. This is a workaround for the issue.
1162
- this.$.items.style.paddingBottom = 0;
1163
- if (!this.allRowsVisible) {
1164
- this.$.items.style.paddingBottom = `${this.$.footer.offsetHeight}px`;
1165
- }
1166
- }
1167
- }
1168
-
1169
- /**
1170
- * @param {!HTMLElement} row
1171
- * @param {GridItem} item
1172
- * @protected
1173
- */
1174
- _updateItem(row, item) {
1175
- row._item = item;
1176
- const model = this.__getRowModel(row);
1177
-
1178
- this._toggleDetailsCell(row, model.detailsOpened);
1179
-
1180
- this._a11yUpdateRowLevel(row, model.level);
1181
- this._a11yUpdateRowSelected(row, model.selected);
1182
-
1183
- this._updateRowStateParts(row, model);
1184
-
1185
- this._generateCellClassNames(row, model);
1186
- this._generateCellPartNames(row, model);
1187
- this._filterDragAndDrop(row, model);
1188
-
1189
- iterateChildren(row, (cell) => {
1190
- if (cell._renderer) {
1191
- const owner = cell._column || this;
1192
- cell._renderer.call(owner, cell._content, owner, model);
1193
- }
1194
- });
1195
-
1196
- this._updateDetailsCellHeight(row);
1197
-
1198
- this._a11yUpdateRowExpanded(row, model.expanded);
1199
- }
1200
-
1201
- /** @private */
1202
- _resizeHandler() {
1203
- this._updateDetailsCellHeights();
1204
- this.__updateFooterPositioning();
1205
- this.__updateHorizontalScrollPosition();
1206
- }
1207
-
1208
- /** @private */
1209
- _onAnimationEnd(e) {
1210
- // ShadyCSS applies scoping suffixes to animation names
1211
- if (e.animationName.indexOf('vaadin-grid-appear') === 0) {
1212
- e.stopPropagation();
1213
- this.__tryToRecalculateColumnWidthsIfPending();
1214
-
1215
- requestAnimationFrame(() => {
1216
- this.__scrollToPendingIndexes();
1217
- });
1218
- }
1219
- }
1220
-
1221
- /**
1222
- * @param {!HTMLTableRowElement} row
1223
- * @return {!GridItemModel}
1224
- * @protected
1225
- */
1226
- __getRowModel(row) {
1227
- return {
1228
- index: row.index,
1229
- item: row._item,
1230
- level: this._getIndexLevel(row.index),
1231
- expanded: this._isExpanded(row._item),
1232
- selected: this._isSelected(row._item),
1233
- detailsOpened: !!this.rowDetailsRenderer && this._isDetailsOpened(row._item),
1234
- };
1235
- }
1236
-
1237
- /**
1238
- * @param {Event} event
1239
- * @protected
1240
- */
1241
- _showTooltip(event) {
1242
- // Check if there is a slotted vaadin-tooltip element.
1243
- const tooltip = this._tooltipController.node;
1244
- if (tooltip && tooltip.isConnected) {
1245
- this._tooltipController.setTarget(event.target);
1246
- this._tooltipController.setContext(this.getEventContext(event));
1247
-
1248
- // Trigger opening using the corresponding delay.
1249
- tooltip._stateController.open({
1250
- focus: event.type === 'focusin',
1251
- hover: event.type === 'mouseenter',
1252
- });
1253
- }
1254
- }
1255
-
1256
- /** @protected */
1257
- _hideTooltip(immediate) {
1258
- const tooltip = this._tooltipController.node;
1259
- if (tooltip) {
1260
- tooltip._stateController.close(immediate);
1261
- }
1262
- }
1263
-
1264
- /**
1265
- * Requests an update for the content of cells.
1266
- *
1267
- * While performing the update, the following renderers are invoked:
1268
- * - `Grid.rowDetailsRenderer`
1269
- * - `GridColumn.renderer`
1270
- * - `GridColumn.headerRenderer`
1271
- * - `GridColumn.footerRenderer`
1272
- *
1273
- * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
1274
- */
1275
- requestContentUpdate() {
1276
- if (this._columnTree) {
1277
- // Header and footer renderers
1278
- this._columnTree.forEach((level) => {
1279
- level.forEach((column) => {
1280
- if (column._renderHeaderAndFooter) {
1281
- column._renderHeaderAndFooter();
1282
- }
1283
- });
1284
- });
1285
-
1286
- // Body and row details renderers
1287
- this.__updateVisibleRows();
1288
- }
1289
- }
1290
-
1291
- /** @protected */
1292
- __updateVisibleRows(start, end) {
1293
- if (this.__virtualizer) {
1294
- this.__virtualizer.update(start, end);
1295
- }
1296
- }
1297
286
  }
1298
287
 
1299
- customElements.define(Grid.is, Grid);
288
+ defineCustomElement(Grid);
1300
289
 
1301
290
  export { Grid };