| // 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(void Function(Set<String> s) f) { | |
| _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 Function(String name) test) { | |
| _removeWhere(_element, test, true); | |
| } | |
| void retainWhere(bool Function(String name) test) { | |
| _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 Function(String name) test, 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); | |
| } | |
| } |