coral-spectrum/coral-collection/src/scripts/SelectableCollection.js
- /**
- * Copyright 2019 Adobe. All rights reserved.
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License. You may obtain a copy
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
- * OF ANY KIND, either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-
- import {commons} from '../../../coral-utils';
- import Collection from './Collection';
- import listToArray from './listToArray';
-
- /**
- Collection capable of handling non-nested items with a selected attribute. It is useful to manage the
- internal state of selection. It currently does not support options.filter for the selection related functions.
- */
- class SelectableCollection extends Collection {
- constructor(options) {
- super(options);
-
- if (this._filter) {
- commons._log('warn', 'Coral.SelectableCollection does not support the options.filter');
- }
-
- // disabled items will not be a selection candicate although hidden items might
- this._selectableItemSelector = this._allItemsSelector.split(',').map(selector => `${selector}:not([disabled])`).join(',');
- this._selectedItemSelector = this._allItemsSelector.split(',').map(selector => `${selector}[selected]`).join(',');
- this._deselectAllExceptSelector = this._selectedItemSelector;
- }
-
- /**
- Returns the selectable items. Items that are disabled quality for selection. On the other hand, hidden items
- can be selected as this is the default behavior in HTML. Please note that an already selected item could be
- returned, since the selection could be toggled.
-
- @returns {Array.<HTMLElement>}
- an array of items whose selection could be toggled.
-
- @protected
- */
- _getSelectableItems() {
- return Array.from(this._container.querySelectorAll(this._selectableItemSelector));
- }
-
- /**
- Returns the first selectable item. Items that are disabled quality for selection. On the other hand, hidden items
- can be selected as this is the default behavior in HTML. Please note that an already selected item could be
- returned, since the selection could be toggled.
-
- @returns {HTMLElement}
- an item whose selection could be toggled.
-
- @protected
- */
- _getFirstSelectable() {
- return this._container.querySelector(this._selectableItemSelector) || null;
- }
-
- /**
- Returns the last selectable item. Items that are disabled quality for selection. On the other hand, hidden items
- can be selected as this is the default behavior in HTML. Please note that an already selected item could be
- returned, since the selection could be toggled.
-
- @returns {HTMLElement}
- an item whose selection could be toggled.
-
- @protected
- */
- _getLastSelectable() {
- const items = this._container.querySelectorAll(this._selectableItemSelector);
- return items[items.length - 1] || null;
- }
-
- /**
- Returns the previous selectable item.
-
- @param {HTMLElement} item
- The reference item.
-
- @returns {HTMLElement}
- an item whose selection could be toggled.
-
- @protected
- */
- _getPreviousSelectable(item) {
- const items = this.getAll();
- let index = items.indexOf(item);
- let sibling = index > 0 ? items[index - 1] : null;
-
- while (sibling) {
- if (sibling.matches(this._selectableItemSelector)) {
- break;
- } else {
- index--;
- sibling = index > 0 ? items[index - 1] : null;
- }
- }
-
- // in case the item is not specified, or it is not inside the collection, we need to return the first selectable
- return sibling || (item.matches(this._selectableItemSelector) ? item : this._getFirstSelectable());
- }
-
- /**
- Returns the next selectable item.
-
- @param {HTMLElement} item
- The reference item.
-
- @returns {HTMLElement}
- an item whose selection could be toggled.
-
- @protected
- */
- _getNextSelectable(item) {
- const items = this.getAll();
- let index = items.indexOf(item);
- let sibling = index < items.length - 1 ? items[index + 1] : null;
-
- while (sibling) {
- if (sibling.matches(this._selectableItemSelector)) {
- break;
- } else {
- index++;
- sibling = index < items.length - 1 ? items[index + 1] : null;
- }
- }
-
- return sibling || item;
- }
-
- /**
- Returns the first item that is selected in the Collection. It allows to configure the attribute used for selection
- so that components that use 'selected' and 'active' can share the same implementation.
-
- @param {String} [selectedAttribute=selected]
- the attribute that will be used to check for selection.
-
- @returns HTMLElement the first selected item.
-
- @protected
- */
- _getFirstSelected(selectedAttribute) {
- let selector = this._selectedItemSelector;
-
- if (typeof selectedAttribute === 'string') {
- selector = selector.replace('[selected]', `[${selectedAttribute}]`);
- }
-
- return this._container.querySelector(selector) || null;
- }
-
- /**
- Returns the last item that is selected in the Collection. It allows to configure the attribute used for selection
- so that components that use 'selected' and 'active' can share the same implementation.
-
- @param {String} [selectedAttribute=selected]
- the attribute that will be used to check for selection.
-
- @returns HTMLElment the last selected item.
-
- @protected
- */
- _getLastSelected(selectedAttribute) {
- let selector = this._selectedItemSelector;
-
- if (typeof selectedAttribute === 'string') {
- selector = selector.replace('[selected]', `[${selectedAttribute}]`);
- }
-
- // last-of-type did not work so we need to query all
- const items = this._container.querySelectorAll(selector);
- return items[items.length - 1] || null;
- }
-
- /**
- Returns an array that contains all the items that are selected.
-
- @param {String} [selectedAttribute=selected]
- the attribute that will be used to check for selection.
-
- @protected
-
- @returns Array.<HTMLElement> an array with all the selected items.
- */
- _getAllSelected(selectedAttribute) {
- let selector = this._selectedItemSelector;
-
- if (typeof selectedAttribute === 'string') {
- selector = selector.replace('[selected]', `[${selectedAttribute}]`);
- }
- return Array.from(this._container.querySelectorAll(selector));
- }
-
- /**
- Deselects all the items except the first selected item in the Collection. By default the <code>selected</code>
- attribute will be removed. The attribute to remove is configurable via the <code>selectedAttribute</code> parameter.
- The selected attribute will be removed no matter if the item is <code>disabled</code> or <code>hidden</code>.
-
- @param {String} [selectedAttribute=selected]
- the attribute that will be used to check for selection. This attribute will be removed from the matching elements.
-
- @protected
- */
- _deselectAllExceptFirst(selectedAttribute) {
- let selector = this._deselectAllExceptSelector;
- const attributeToRemove = selectedAttribute || 'selected';
-
- if (typeof selectedAttribute === 'string') {
- selector = selector.replace('[selected]', `[${selectedAttribute}]`);
- }
-
- // we select all the selected attributes except the last one
- const items = this._container.querySelectorAll(selector);
- const itemsCount = items.length;
-
- // ignores the first item of the list, everything else is deselected
- for (let i = 1 ; i < itemsCount ; i++) {
- // we use remoteAttribute since we do not know if the element is upgraded
- items[i].removeAttribute(attributeToRemove);
- }
- }
-
- /**
- Deselects all the items except the last selected item in the Collecton. By default the <code>selected</code>
- attribute will be removed. The attribute to remove is configurable via the <code>selectedAttribute</code> parameter.
-
- @param {String} [selectedAttribute=selected]
- the attribute that will be used to check for selection. This attribute will be removed from the matching elements.
-
- @protected
- */
- _deselectAllExceptLast(selectedAttribute) {
- let selector = this._deselectAllExceptSelector;
- const attributeToRemove = selectedAttribute || 'selected';
-
- if (typeof selectedAttribute === 'string') {
- selector = selector.replace('[selected]', `[${selectedAttribute}]`);
- }
-
- // we query for all matching items with the given attribute
- const items = this._container.querySelectorAll(selector);
- // we ignore the last item
- const itemsCount = items.length - 1;
-
- for (let i = 0 ; i < itemsCount ; i++) {
- // we use remoteAttribute since we do not know if the element is upgraded
- items[i].removeAttribute(attributeToRemove);
- }
- }
-
- /**
- Deselects all the items except the given item. The provided attribute will be remove from all matching items. By
- default the <code>selected</code> attribute will be removed. The attribute to remove is configurable via the
- <code>selectedAttribute</code> parameter.
-
- @name Coral.SelectableCollection#_deselectAllExcept
- @function
-
- @param {HTMLElement} [itemOrSelectedAttribute]
- The item to keep selected. If the item is not provided, all elements will be deselected.
-
- @param {String} [selectedAttribute=selected]
- the attribute that will be used to check for selection. This attribute will be removed from the matching elements.
-
- @protected
- */
- _deselectAllExcept(itemOrSelectedAttribute, selectedAttribute) {
- // if no selectedAttribute we use the unmodified selector as default
- let selector = this._deselectAllExceptSelector;
-
- let item;
- let attributeToRemove;
- // an item was not provided so we use it as 'selectedAttribute'
- if (typeof itemOrSelectedAttribute === 'string') {
- item = null;
- attributeToRemove = itemOrSelectedAttribute || 'selected';
- selector = selector.replace('[selected]', `[${attributeToRemove}]`);
- } else {
- item = itemOrSelectedAttribute;
- attributeToRemove = selectedAttribute || 'selected';
-
- if (typeof selectedAttribute === 'string') {
- selector = selector.replace('[selected]', `[${attributeToRemove}]`);
- }
- }
-
- // we query for all matching items with the given attribute
- const items = this._container.querySelectorAll(selector);
- const itemsCount = items.length;
-
- for (let i = 0 ; i < itemsCount ; i++) {
- // we use remoteAttribute since we do not know if the element is upgraded
- if (item !== items[i]) {
- items[i].removeAttribute(attributeToRemove);
- }
- }
- }
- }
-
- export default SelectableCollection;