From 7e21db19092a1aa5673aecb40559c1f8ba02344c Mon Sep 17 00:00:00 2001 From: Zach Arend Date: Thu, 3 Aug 2023 18:12:40 +0000 Subject: [PATCH] docs(material/tree): delete tree-checklist example. Delete tree-checklist example. That's because this example doesn't correctly follow [ARIA Tree View interaction](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/) for selection. A correct solution would use aria-selected or aria-checked. --- .../material/tree/index.ts | 1 - .../tree-checklist/tree-checklist-example.css | 3 - .../tree-checklist-example.html | 30 -- .../tree-checklist/tree-checklist-example.ts | 280 ------------------ src/dev-app/tree/tree-demo.html | 4 - src/dev-app/tree/tree-demo.ts | 2 - 6 files changed, 320 deletions(-) delete mode 100644 src/components-examples/material/tree/tree-checklist/tree-checklist-example.css delete mode 100644 src/components-examples/material/tree/tree-checklist/tree-checklist-example.html delete mode 100644 src/components-examples/material/tree/tree-checklist/tree-checklist-example.ts diff --git a/src/components-examples/material/tree/index.ts b/src/components-examples/material/tree/index.ts index 1b47ab706284..dd5eed1712d9 100644 --- a/src/components-examples/material/tree/index.ts +++ b/src/components-examples/material/tree/index.ts @@ -1,4 +1,3 @@ -export {TreeChecklistExample} from './tree-checklist/tree-checklist-example'; export {TreeDynamicExample} from './tree-dynamic/tree-dynamic-example'; export {TreeFlatOverviewExample} from './tree-flat-overview/tree-flat-overview-example'; export {TreeHarnessExample} from './tree-harness/tree-harness-example'; diff --git a/src/components-examples/material/tree/tree-checklist/tree-checklist-example.css b/src/components-examples/material/tree/tree-checklist/tree-checklist-example.css deleted file mode 100644 index c5ab9948f1a2..000000000000 --- a/src/components-examples/material/tree/tree-checklist/tree-checklist-example.css +++ /dev/null @@ -1,3 +0,0 @@ -.mat-mdc-form-field { - margin-right: 4px; -} diff --git a/src/components-examples/material/tree/tree-checklist/tree-checklist-example.html b/src/components-examples/material/tree/tree-checklist/tree-checklist-example.html deleted file mode 100644 index cbe7f96d03f5..000000000000 --- a/src/components-examples/material/tree/tree-checklist/tree-checklist-example.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - {{node.item}} - - - - - - New item... - - - - - - - - {{node.item}} - - - diff --git a/src/components-examples/material/tree/tree-checklist/tree-checklist-example.ts b/src/components-examples/material/tree/tree-checklist/tree-checklist-example.ts deleted file mode 100644 index b3e9deecc893..000000000000 --- a/src/components-examples/material/tree/tree-checklist/tree-checklist-example.ts +++ /dev/null @@ -1,280 +0,0 @@ -import {SelectionModel} from '@angular/cdk/collections'; -import {FlatTreeControl} from '@angular/cdk/tree'; -import {Component, Injectable} from '@angular/core'; -import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree'; -import {MatIconModule} from '@angular/material/icon'; -import {MatInputModule} from '@angular/material/input'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatCheckboxModule} from '@angular/material/checkbox'; -import {MatButtonModule} from '@angular/material/button'; -import {BehaviorSubject} from 'rxjs'; - -/** - * Node for to-do item - */ -export class TodoItemNode { - children: TodoItemNode[]; - item: string; -} - -/** Flat to-do item node with expandable and level information */ -export class TodoItemFlatNode { - item: string; - level: number; - expandable: boolean; -} - -/** - * The Json object for to-do list data. - */ -const TREE_DATA = { - Groceries: { - 'Almond Meal flour': null, - 'Organic eggs': null, - 'Protein Powder': null, - Fruits: { - Apple: null, - Berries: ['Blueberry', 'Raspberry'], - Orange: null, - }, - }, - Reminders: ['Cook dinner', 'Read the Material Design spec', 'Upgrade Application to Angular'], -}; - -/** - * Checklist database, it can build a tree structured Json object. - * Each node in Json object represents a to-do item or a category. - * If a node is a category, it has children items and new items can be added under the category. - */ -@Injectable() -export class ChecklistDatabase { - dataChange = new BehaviorSubject([]); - - get data(): TodoItemNode[] { - return this.dataChange.value; - } - - constructor() { - this.initialize(); - } - - initialize() { - // Build the tree nodes from Json object. The result is a list of `TodoItemNode` with nested - // file node as children. - const data = this.buildFileTree(TREE_DATA, 0); - - // Notify the change. - this.dataChange.next(data); - } - - /** - * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. - * The return value is the list of `TodoItemNode`. - */ - buildFileTree(obj: {[key: string]: any}, level: number): TodoItemNode[] { - return Object.keys(obj).reduce((accumulator, key) => { - const value = obj[key]; - const node = new TodoItemNode(); - node.item = key; - - if (value != null) { - if (typeof value === 'object') { - node.children = this.buildFileTree(value, level + 1); - } else { - node.item = value; - } - } - - return accumulator.concat(node); - }, []); - } - - /** Add an item to to-do list */ - insertItem(parent: TodoItemNode, name: string) { - if (parent.children) { - parent.children.push({item: name} as TodoItemNode); - this.dataChange.next(this.data); - } - } - - updateItem(node: TodoItemNode, name: string) { - node.item = name; - this.dataChange.next(this.data); - } -} - -/** - * @title Tree with checkboxes - */ -@Component({ - selector: 'tree-checklist-example', - templateUrl: 'tree-checklist-example.html', - styleUrls: ['tree-checklist-example.css'], - providers: [ChecklistDatabase], - standalone: true, - imports: [ - MatTreeModule, - MatButtonModule, - MatCheckboxModule, - MatFormFieldModule, - MatInputModule, - MatIconModule, - ], -}) -export class TreeChecklistExample { - /** Map from flat node to nested node. This helps us finding the nested node to be modified */ - flatNodeMap = new Map(); - - /** Map from nested node to flattened node. This helps us to keep the same object for selection */ - nestedNodeMap = new Map(); - - /** A selected parent node to be inserted */ - selectedParent: TodoItemFlatNode | null = null; - - /** The new item's name */ - newItemName = ''; - - treeControl: FlatTreeControl; - - treeFlattener: MatTreeFlattener; - - dataSource: MatTreeFlatDataSource; - - /** The selection for checklist */ - checklistSelection = new SelectionModel(true /* multiple */); - - constructor(private _database: ChecklistDatabase) { - this.treeFlattener = new MatTreeFlattener( - this.transformer, - this.getLevel, - this.isExpandable, - this.getChildren, - ); - this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); - this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); - - _database.dataChange.subscribe(data => { - this.dataSource.data = data; - }); - } - - getLevel = (node: TodoItemFlatNode) => node.level; - - isExpandable = (node: TodoItemFlatNode) => node.expandable; - - getChildren = (node: TodoItemNode): TodoItemNode[] => node.children; - - hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable; - - hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === ''; - - /** - * Transformer to convert nested node to flat node. Record the nodes in maps for later use. - */ - transformer = (node: TodoItemNode, level: number) => { - const existingNode = this.nestedNodeMap.get(node); - const flatNode = - existingNode && existingNode.item === node.item ? existingNode : new TodoItemFlatNode(); - flatNode.item = node.item; - flatNode.level = level; - flatNode.expandable = !!node.children?.length; - this.flatNodeMap.set(flatNode, node); - this.nestedNodeMap.set(node, flatNode); - return flatNode; - }; - - /** Whether all the descendants of the node are selected. */ - descendantsAllSelected(node: TodoItemFlatNode): boolean { - const descendants = this.treeControl.getDescendants(node); - const descAllSelected = - descendants.length > 0 && - descendants.every(child => { - return this.checklistSelection.isSelected(child); - }); - return descAllSelected; - } - - /** Whether part of the descendants are selected */ - descendantsPartiallySelected(node: TodoItemFlatNode): boolean { - const descendants = this.treeControl.getDescendants(node); - const result = descendants.some(child => this.checklistSelection.isSelected(child)); - return result && !this.descendantsAllSelected(node); - } - - /** Toggle the to-do item selection. Select/deselect all the descendants node */ - todoItemSelectionToggle(node: TodoItemFlatNode): void { - this.checklistSelection.toggle(node); - const descendants = this.treeControl.getDescendants(node); - this.checklistSelection.isSelected(node) - ? this.checklistSelection.select(...descendants) - : this.checklistSelection.deselect(...descendants); - - // Force update for the parent - descendants.forEach(child => this.checklistSelection.isSelected(child)); - this.checkAllParentsSelection(node); - } - - /** Toggle a leaf to-do item selection. Check all the parents to see if they changed */ - todoLeafItemSelectionToggle(node: TodoItemFlatNode): void { - this.checklistSelection.toggle(node); - this.checkAllParentsSelection(node); - } - - /* Checks all the parents when a leaf node is selected/unselected */ - checkAllParentsSelection(node: TodoItemFlatNode): void { - let parent: TodoItemFlatNode | null = this.getParentNode(node); - while (parent) { - this.checkRootNodeSelection(parent); - parent = this.getParentNode(parent); - } - } - - /** Check root node checked state and change it accordingly */ - checkRootNodeSelection(node: TodoItemFlatNode): void { - const nodeSelected = this.checklistSelection.isSelected(node); - const descendants = this.treeControl.getDescendants(node); - const descAllSelected = - descendants.length > 0 && - descendants.every(child => { - return this.checklistSelection.isSelected(child); - }); - if (nodeSelected && !descAllSelected) { - this.checklistSelection.deselect(node); - } else if (!nodeSelected && descAllSelected) { - this.checklistSelection.select(node); - } - } - - /* Get the parent node of a node */ - getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null { - const currentLevel = this.getLevel(node); - - if (currentLevel < 1) { - return null; - } - - const startIndex = this.treeControl.dataNodes.indexOf(node) - 1; - - for (let i = startIndex; i >= 0; i--) { - const currentNode = this.treeControl.dataNodes[i]; - - if (this.getLevel(currentNode) < currentLevel) { - return currentNode; - } - } - return null; - } - - /** Select the category so we can insert the new item. */ - addNewItem(node: TodoItemFlatNode) { - const parentNode = this.flatNodeMap.get(node); - this._database.insertItem(parentNode!, ''); - this.treeControl.expand(node); - } - - /** Save the node to database */ - saveNode(node: TodoItemFlatNode, itemValue: string) { - const nestedNode = this.flatNodeMap.get(node); - this._database.updateItem(nestedNode!, itemValue); - } -} diff --git a/src/dev-app/tree/tree-demo.html b/src/dev-app/tree/tree-demo.html index 97160db7c1cb..8b2d4e1fedd6 100644 --- a/src/dev-app/tree/tree-demo.html +++ b/src/dev-app/tree/tree-demo.html @@ -15,10 +15,6 @@ CDK Nested tree - - Todo list tree - - Dynamic flat tree diff --git a/src/dev-app/tree/tree-demo.ts b/src/dev-app/tree/tree-demo.ts index af0ae1ed9d98..ebcf0e40e575 100644 --- a/src/dev-app/tree/tree-demo.ts +++ b/src/dev-app/tree/tree-demo.ts @@ -10,7 +10,6 @@ import {CdkTreeModule} from '@angular/cdk/tree'; import {CommonModule} from '@angular/common'; import {CdkTreeFlatExample, CdkTreeNestedExample} from '@angular/components-examples/cdk/tree'; import { - TreeChecklistExample, TreeDynamicExample, TreeFlatOverviewExample, TreeHarnessExample, @@ -38,7 +37,6 @@ import {MatTreeModule} from '@angular/material/tree'; CdkTreeNestedExample, CommonModule, FormsModule, - TreeChecklistExample, TreeDynamicExample, TreeFlatOverviewExample, TreeHarnessExample,