From bd46df08b87eb54e8b3d5a7ee01abf5c7ec43df1 Mon Sep 17 00:00:00 2001 From: lukasnys Date: Tue, 23 Jul 2024 13:41:17 +0200 Subject: [PATCH 1/8] feat: add isGroupIndeterminate getter to rowMeta --- addon/-private/collapse-tree.js | 26 ++++++++++++++++++++++++++ types/index.d.ts | 1 + 2 files changed, 27 insertions(+) diff --git a/addon/-private/collapse-tree.js b/addon/-private/collapse-tree.js index 4410b0584..1f42a7c8b 100644 --- a/addon/-private/collapse-tree.js +++ b/addon/-private/collapse-tree.js @@ -83,6 +83,32 @@ export const TableRowMeta = EmberObject.extend({ } ), + isGroupIndeterminate: computed( + '_tree.{selection.[],selectionMatchFunction}', + '_parentMeta.isSelected', + function() { + let rowValue = get(this, '_rowValue'); + let selection = get(this, '_tree.selection'); + let selectionMatchFunction = get(this, '_tree.selectionMatchFunction'); + + if (!rowValue.children || !isArray(rowValue.children)) { + return false; + } + + if (!selection || !isArray(selection)) { + return false; + } + + if (selectionMatchFunction) { + return rowValue.children.some(child => + selection.some(item => selectionMatchFunction(item, child)) + ); + } + + return rowValue.children.some(child => selection.includes(child)); + } + ), + canCollapse: computed( '_tree.{enableTree,enableCollapse}', '_rowValue.{children.[],disableCollapse}', diff --git a/types/index.d.ts b/types/index.d.ts index 523915fe9..b70545c0b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -23,6 +23,7 @@ export interface TableRowMeta { isCollapsed: boolean; isSelected: boolean; isGroupSelected: boolean; + isGroupIndeterminate: boolean; canCollapse: boolean; depth: number; first: unknown | null; From 788b6070187a5067a8ff970119db5fed8c6886ac Mon Sep 17 00:00:00 2001 From: lukasnys Date: Tue, 23 Jul 2024 13:43:35 +0200 Subject: [PATCH 2/8] feat: display indeterminate state --- addon/components/ember-td/template.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/components/ember-td/template.hbs b/addon/components/ember-td/template.hbs index 4cbab5045..01f22d169 100644 --- a/addon/components/ember-td/template.hbs +++ b/addon/components/ember-td/template.hbs @@ -7,6 +7,7 @@ > Date: Tue, 23 Jul 2024 13:43:47 +0200 Subject: [PATCH 3/8] feat: add is-group-indeterminate class binding --- addon/components/ember-tr/component.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addon/components/ember-tr/component.js b/addon/components/ember-tr/component.js index 4413d63e3..c2c07885c 100644 --- a/addon/components/ember-tr/component.js +++ b/addon/components/ember-tr/component.js @@ -48,7 +48,7 @@ export default Component.extend({ layout, tagName: 'tr', classNames: ['et-tr'], - classNameBindings: ['isSelected', 'isGroupSelected', 'isSelectable'], + classNameBindings: ['isSelected', 'isGroupSelected', 'isGroupIndeterminate', 'isSelectable'], /** The API object passed in by the table body, header, or footer @@ -88,6 +88,8 @@ export default Component.extend({ isGroupSelected: readOnly('rowMeta.isGroupSelected'), + isGroupIndeterminate: readOnly('rowMeta.isGroupIndeterminate'), + isSelectable: computed('rowSelectionMode', function() { let rowSelectionMode = this.get('rowSelectionMode'); From de45530ae5f9a4f38eff7421f3c43703c68e7a11 Mon Sep 17 00:00:00 2001 From: lukasnys Date: Tue, 23 Jul 2024 13:44:31 +0200 Subject: [PATCH 4/8] test: write tests for indeterminate state --- .../pages/-private/ember-table-body.js | 3 +++ .../integration/components/selection-test.js | 26 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/addon-test-support/pages/-private/ember-table-body.js b/addon-test-support/pages/-private/ember-table-body.js index 6bb3835fb..60cd7054e 100644 --- a/addon-test-support/pages/-private/ember-table-body.js +++ b/addon-test-support/pages/-private/ember-table-body.js @@ -75,6 +75,7 @@ export default PageObject.extend({ checkbox: { scope: '[data-test-select-row]', isChecked: property('checked'), + isIndeterminate: property('indeterminate'), async clickWith(options) { await click(findElement(this), options); @@ -98,6 +99,8 @@ export default PageObject.extend({ isSelected: hasClass('is-selected'), + isGroupIndeterminate: hasClass('is-group-indeterminate'), + /** Helper function to click with options like the meta key and ctrl key set diff --git a/tests/integration/components/selection-test.js b/tests/integration/components/selection-test.js index 68055a7b7..e20ac033b 100644 --- a/tests/integration/components/selection-test.js +++ b/tests/integration/components/selection-test.js @@ -2,7 +2,6 @@ import { module, test } from 'qunit'; import { componentModule } from '../../helpers/module'; import TablePage from 'ember-table/test-support/pages/ember-table'; - import { generateTable } from '../../helpers/generate-table'; import { generateRows } from 'dummy/utils/generators'; import { A as emberA } from '@ember/array'; @@ -483,6 +482,31 @@ module('Integration | selection', () => { assert.ok(row.isSelected, 'the row is selected'); assert.ok(!row.checkbox.isChecked, 'the row checkbox is checked'); }); + + test('Selecting a child row causes parent checkbox to be indeterminate', async function(assert) { + await generateTable(this, { rowCount: 3, rowDepth: 2 }); + + let parentRow = table.rows.objectAt(0); + let childRow = table.rows.objectAt(1); + + assert.ok(!parentRow.isSelected, 'parent row is not selected'); + assert.ok(!parentRow.isGroupIndeterminate, 'parent row is not indeterminate'); + + assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked'); + assert.ok(!parentRow.checkbox.isIndeterminate, 'parent row checkbox is not indeterminate'); + + assert.ok(!childRow.isSelected, 'child row is not selected'); + + await childRow.checkbox.click(); + + assert.ok(childRow.isSelected, 'child row is selected'); + + assert.ok(!parentRow.isSelected, 'parent row is not selected'); + assert.ok(parentRow.isGroupIndeterminate, 'parent row is indeterminate'); + + assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked'); + assert.ok(parentRow.checkbox.isIndeterminate, 'parent row checkbox is indeterminate'); + }); }); componentModule('single', function() { From a896e0c34fcb77be0eb85beb393722e5b4769bd9 Mon Sep 17 00:00:00 2001 From: lukasnys Date: Tue, 23 Jul 2024 13:45:37 +0200 Subject: [PATCH 5/8] test: write tests for indeterminate state --- .../integration/components/selection-test.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/integration/components/selection-test.js b/tests/integration/components/selection-test.js index e20ac033b..3d778368f 100644 --- a/tests/integration/components/selection-test.js +++ b/tests/integration/components/selection-test.js @@ -556,6 +556,31 @@ module('Integration | selection', () => { await table.selectRow(0); }); + + test('Selecting a child row causes parent checkbox to be indeterminate', async function(assert) { + await generateTable(this, { rowCount: 3, rowDepth: 2, checkboxSelectionMode: 'single' }); + + let parentRow = table.rows.objectAt(0); + let childRow = table.rows.objectAt(1); + + assert.ok(!parentRow.isSelected, 'parent row is not selected'); + assert.ok(!parentRow.isGroupIndeterminate, 'parent row is not indeterminate'); + + assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked'); + assert.ok(!parentRow.checkbox.isIndeterminate, 'parent row checkbox is not indeterminate'); + + assert.ok(!childRow.isSelected, 'child row is not selected'); + + await childRow.checkbox.click(); + + assert.ok(childRow.isSelected, 'child row is selected'); + + assert.ok(!parentRow.isSelected, 'parent row is not selected'); + assert.ok(parentRow.isGroupIndeterminate, 'parent row is indeterminate'); + + assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked'); + assert.ok(parentRow.checkbox.isIndeterminate, 'parent row checkbox is indeterminate'); + }); }); componentModule('none', function() { From 85abb7f6f49e2f95bcda6875637760c42ddda217 Mon Sep 17 00:00:00 2001 From: lukasnys Date: Tue, 23 Jul 2024 13:50:01 +0200 Subject: [PATCH 6/8] cleanup: remove unused property from computed watchlist --- addon/-private/collapse-tree.js | 38 +++++++++++++++------------------ 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/addon/-private/collapse-tree.js b/addon/-private/collapse-tree.js index 1f42a7c8b..fba11d785 100644 --- a/addon/-private/collapse-tree.js +++ b/addon/-private/collapse-tree.js @@ -83,31 +83,27 @@ export const TableRowMeta = EmberObject.extend({ } ), - isGroupIndeterminate: computed( - '_tree.{selection.[],selectionMatchFunction}', - '_parentMeta.isSelected', - function() { - let rowValue = get(this, '_rowValue'); - let selection = get(this, '_tree.selection'); - let selectionMatchFunction = get(this, '_tree.selectionMatchFunction'); - - if (!rowValue.children || !isArray(rowValue.children)) { - return false; - } + isGroupIndeterminate: computed('_tree.{selection.[],selectionMatchFunction}', function() { + let rowValue = get(this, '_rowValue'); + let selection = get(this, '_tree.selection'); + let selectionMatchFunction = get(this, '_tree.selectionMatchFunction'); - if (!selection || !isArray(selection)) { - return false; - } + if (!rowValue.children || !isArray(rowValue.children)) { + return false; + } - if (selectionMatchFunction) { - return rowValue.children.some(child => - selection.some(item => selectionMatchFunction(item, child)) - ); - } + if (!selection || !isArray(selection)) { + return false; + } - return rowValue.children.some(child => selection.includes(child)); + if (selectionMatchFunction) { + return rowValue.children.some(child => + selection.some(item => selectionMatchFunction(item, child)) + ); } - ), + + return rowValue.children.some(child => selection.includes(child)); + }), canCollapse: computed( '_tree.{enableTree,enableCollapse}', From 3204b4a64e7737900314377c70a60780eca14d24 Mon Sep 17 00:00:00 2001 From: lukasnys Date: Tue, 23 Jul 2024 13:55:08 +0200 Subject: [PATCH 7/8] feat: add check for selectingChildrenSelectsParent in which case we don't show the indeterminate state, since checking children then doesn't "work towards" selecting the parent --- addon/-private/collapse-tree.js | 42 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/addon/-private/collapse-tree.js b/addon/-private/collapse-tree.js index fba11d785..3c60fed2f 100644 --- a/addon/-private/collapse-tree.js +++ b/addon/-private/collapse-tree.js @@ -83,27 +83,35 @@ export const TableRowMeta = EmberObject.extend({ } ), - isGroupIndeterminate: computed('_tree.{selection.[],selectionMatchFunction}', function() { - let rowValue = get(this, '_rowValue'); - let selection = get(this, '_tree.selection'); - let selectionMatchFunction = get(this, '_tree.selectionMatchFunction'); + isGroupIndeterminate: computed( + '_tree.{selection.[],selectionMatchFunction,selectingChildrenSelectsParent}', + function() { + let rowValue = get(this, '_rowValue'); + let selection = get(this, '_tree.selection'); + let selectionMatchFunction = get(this, '_tree.selectionMatchFunction'); + let selectingChildrenSelectsParent = get(this, '_tree.selectingChildrenSelectsParent'); - if (!rowValue.children || !isArray(rowValue.children)) { - return false; - } + if (!rowValue.children || !isArray(rowValue.children)) { + return false; + } - if (!selection || !isArray(selection)) { - return false; - } + if (!selection || !isArray(selection)) { + return false; + } - if (selectionMatchFunction) { - return rowValue.children.some(child => - selection.some(item => selectionMatchFunction(item, child)) - ); - } + if (!selectingChildrenSelectsParent) { + return false; + } - return rowValue.children.some(child => selection.includes(child)); - }), + if (selectionMatchFunction) { + return rowValue.children.some(child => + selection.some(item => selectionMatchFunction(item, child)) + ); + } + + return rowValue.children.some(child => selection.includes(child)); + } + ), canCollapse: computed( '_tree.{enableTree,enableCollapse}', From c4bc3ec58bd50770183ba847927ee526d51ddfa9 Mon Sep 17 00:00:00 2001 From: lukasnys Date: Thu, 28 Nov 2024 10:13:22 +0100 Subject: [PATCH 8/8] empty commit to re-trigger CI