|  | // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | part of html; | 
|  |  | 
|  | /** | 
|  | * A set (union) of the CSS classes that are present in a set of elements. | 
|  | * Implemented separately from _ElementCssClassSet for performance. | 
|  | */ | 
|  | class _MultiElementCssClassSet extends CssClassSetImpl { | 
|  | final Iterable<Element> _elementIterable; | 
|  |  | 
|  | // TODO(sra): Perhaps we should store the DomTokenList instead. | 
|  | final List<CssClassSetImpl> _sets; | 
|  |  | 
|  | factory _MultiElementCssClassSet(Iterable<Element> elements) { | 
|  | return new _MultiElementCssClassSet._(elements, | 
|  | new List<CssClassSetImpl>.from(elements.map((Element e) => e.classes))); | 
|  | } | 
|  |  | 
|  | _MultiElementCssClassSet._(this._elementIterable, this._sets); | 
|  |  | 
|  | Set<String> readClasses() { | 
|  | var s = new LinkedHashSet<String>(); | 
|  | _sets.forEach((CssClassSetImpl e) => s.addAll(e.readClasses())); | 
|  | return s; | 
|  | } | 
|  |  | 
|  | void writeClasses(Set<String> s) { | 
|  | var classes = s.join(' '); | 
|  | for (Element e in _elementIterable) { | 
|  | e.className = classes; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper method used to modify the set of css classes on this element. | 
|  | * | 
|  | *   f - callback with: | 
|  | *   s - a Set of all the css class name currently on this element. | 
|  | * | 
|  | *   After f returns, the modified set is written to the | 
|  | *       className property of this element. | 
|  | */ | 
|  | modify(f(Set<String> s)) { | 
|  | _sets.forEach((CssClassSetImpl e) => e.modify(f)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds the class [value] to the element if it is not on it, removes it if it | 
|  | * is. | 
|  | * | 
|  | * TODO(sra): It seems wrong to collect a 'changed' flag like this when the | 
|  | * underlying toggle returns an 'is set' flag. | 
|  | */ | 
|  | bool toggle(String value, [bool? shouldAdd]) => _sets.fold( | 
|  | false, | 
|  | (bool changed, CssClassSetImpl e) => | 
|  | e.toggle(value, shouldAdd) || changed); | 
|  |  | 
|  | /** | 
|  | * Remove the class [value] from element, and return true on successful | 
|  | * removal. | 
|  | * | 
|  | * This is the Dart equivalent of jQuery's | 
|  | * [removeClass](http://api.jquery.com/removeClass/). | 
|  | */ | 
|  | bool remove(Object? value) => _sets.fold( | 
|  | false, (bool changed, CssClassSetImpl e) => e.remove(value) || changed); | 
|  | } | 
|  |  | 
|  | class _ElementCssClassSet extends CssClassSetImpl { | 
|  | final Element _element; | 
|  |  | 
|  | _ElementCssClassSet(this._element); | 
|  |  | 
|  | Set<String> readClasses() { | 
|  | var s = new LinkedHashSet<String>(); | 
|  | var classname = _element.className; | 
|  |  | 
|  | for (String name in classname.split(' ')) { | 
|  | String trimmed = name.trim(); | 
|  | if (!trimmed.isEmpty) { | 
|  | s.add(trimmed); | 
|  | } | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | void writeClasses(Set<String> s) { | 
|  | _element.className = s.join(' '); | 
|  | } | 
|  |  | 
|  | int get length => _classListLength(_classListOf(_element)); | 
|  | bool get isEmpty => length == 0; | 
|  | bool get isNotEmpty => length != 0; | 
|  |  | 
|  | void clear() { | 
|  | _element.className = ''; | 
|  | } | 
|  |  | 
|  | bool contains(Object? value) { | 
|  | return _contains(_element, value); | 
|  | } | 
|  |  | 
|  | bool add(String value) { | 
|  | return _add(_element, value); | 
|  | } | 
|  |  | 
|  | bool remove(Object? value) { | 
|  | return value is String && _remove(_element, value); | 
|  | } | 
|  |  | 
|  | bool toggle(String value, [bool? shouldAdd]) { | 
|  | return _toggle(_element, value, shouldAdd); | 
|  | } | 
|  |  | 
|  | void addAll(Iterable<String> iterable) { | 
|  | _addAll(_element, iterable); | 
|  | } | 
|  |  | 
|  | void removeAll(Iterable<Object?> iterable) { | 
|  | _removeAll(_element, iterable); | 
|  | } | 
|  |  | 
|  | void retainAll(Iterable<Object?> iterable) { | 
|  | _removeWhere(_element, iterable.toSet().contains, false); | 
|  | } | 
|  |  | 
|  | void removeWhere(bool test(String name)) { | 
|  | _removeWhere(_element, test, true); | 
|  | } | 
|  |  | 
|  | void retainWhere(bool test(String name)) { | 
|  | _removeWhere(_element, test, false); | 
|  | } | 
|  |  | 
|  | static bool _contains(Element _element, Object? value) { | 
|  | return value is String && _classListContains(_classListOf(_element), value); | 
|  | } | 
|  |  | 
|  | @pragma('dart2js:tryInline') | 
|  | static bool _add(Element _element, String value) { | 
|  | DomTokenList list = _classListOf(_element); | 
|  | // Compute returned result independently of action upon the set. | 
|  | bool added = !_classListContainsBeforeAddOrRemove(list, value); | 
|  | _classListAdd(list, value); | 
|  | return added; | 
|  | } | 
|  |  | 
|  | @pragma('dart2js:tryInline') | 
|  | static bool _remove(Element _element, String value) { | 
|  | DomTokenList list = _classListOf(_element); | 
|  | bool removed = _classListContainsBeforeAddOrRemove(list, value); | 
|  | _classListRemove(list, value); | 
|  | return removed; | 
|  | } | 
|  |  | 
|  | static bool _toggle(Element _element, String value, bool? shouldAdd) { | 
|  | // There is no value that can be passed as the second argument of | 
|  | // DomTokenList.toggle that behaves the same as passing one argument. | 
|  | // `null` is seen as false, meaning 'remove'. | 
|  | return shouldAdd == null | 
|  | ? _toggleDefault(_element, value) | 
|  | : _toggleOnOff(_element, value, shouldAdd); | 
|  | } | 
|  |  | 
|  | static bool _toggleDefault(Element _element, String value) { | 
|  | DomTokenList list = _classListOf(_element); | 
|  | return _classListToggle1(list, value); | 
|  | } | 
|  |  | 
|  | static bool _toggleOnOff(Element _element, String value, bool? shouldAdd) { | 
|  | DomTokenList list = _classListOf(_element); | 
|  | // IE's toggle does not take a second parameter. We would prefer: | 
|  | // | 
|  | //    return _classListToggle2(list, value, shouldAdd); | 
|  | // | 
|  | if (shouldAdd ?? false) { | 
|  | _classListAdd(list, value); | 
|  | return true; | 
|  | } else { | 
|  | _classListRemove(list, value); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void _addAll(Element _element, Iterable<String> iterable) { | 
|  | DomTokenList list = _classListOf(_element); | 
|  | for (String value in iterable) { | 
|  | _classListAdd(list, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void _removeAll(Element _element, Iterable<Object?> iterable) { | 
|  | DomTokenList list = _classListOf(_element); | 
|  | for (Object? value in iterable) { | 
|  | _classListRemove(list, value as String); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void _removeWhere( | 
|  | Element _element, bool test(String name), bool doRemove) { | 
|  | DomTokenList list = _classListOf(_element); | 
|  | int i = 0; | 
|  | while (i < _classListLength(list)) { | 
|  | String item = list.item(i)!; | 
|  | if (doRemove == test(item)) { | 
|  | _classListRemove(list, item); | 
|  | } else { | 
|  | ++i; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // A collection of static methods for DomTokenList. These methods are a | 
|  | // workaround for the lack of annotations to express the full behaviour of | 
|  | // the DomTokenList methods. | 
|  |  | 
|  | static DomTokenList _classListOf(Element e) => JS( | 
|  | 'returns:DomTokenList;creates:DomTokenList;effects:none;depends:all;', | 
|  | '#.classList', | 
|  | e); | 
|  |  | 
|  | static int _classListLength(DomTokenList list) => | 
|  | JS('returns:JSUInt31;effects:none;depends:all;', '#.length', list); | 
|  |  | 
|  | static bool _classListContains(DomTokenList list, String value) => | 
|  | JS('returns:bool;effects:none;depends:all', '#.contains(#)', list, value); | 
|  |  | 
|  | static bool _classListContainsBeforeAddOrRemove( | 
|  | DomTokenList list, String value) => | 
|  | // 'throws:never' is a lie, since 'contains' will throw on an illegal | 
|  | // token.  However, we always call this function immediately prior to | 
|  | // add/remove/toggle with the same token.  Often the result of 'contains' | 
|  | // is unused and the lie makes it possible for the 'contains' instruction | 
|  | // to be removed. | 
|  | JS('returns:bool;effects:none;depends:all;throws:null(1)', | 
|  | '#.contains(#)', list, value); | 
|  |  | 
|  | static void _classListAdd(DomTokenList list, String value) { | 
|  | // list.add(value); | 
|  | JS('', '#.add(#)', list, value); | 
|  | } | 
|  |  | 
|  | static void _classListRemove(DomTokenList list, String value) { | 
|  | // list.remove(value); | 
|  | JS('', '#.remove(#)', list, value); | 
|  | } | 
|  |  | 
|  | static bool _classListToggle1(DomTokenList list, String value) { | 
|  | return JS('bool', '#.toggle(#)', list, value); | 
|  | } | 
|  |  | 
|  | static bool _classListToggle2( | 
|  | DomTokenList list, String value, bool? shouldAdd) { | 
|  | return JS('bool', '#.toggle(#, #)', list, value, shouldAdd); | 
|  | } | 
|  | } |