diff --git a/IPython/html/widgets/__init__.py b/IPython/html/widgets/__init__.py
index 8b19449..bd8d237 100644
--- a/IPython/html/widgets/__init__.py
+++ b/IPython/html/widgets/__init__.py
@@ -1,5 +1,7 @@
from .widget import Widget, DOMWidget, CallbackDispatcher, register
+from .trait_types import Color
+
from .widget_bool import Checkbox, ToggleButton
from .widget_button import Button
from .widget_box import Box, FlexBox, HBox, VBox
diff --git a/IPython/html/widgets/tests/test_traits.py b/IPython/html/widgets/tests/test_traits.py
new file mode 100644
index 0000000..c25960a
--- /dev/null
+++ b/IPython/html/widgets/tests/test_traits.py
@@ -0,0 +1,20 @@
+"""Test trait types of the widget packages."""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from unittest import TestCase
+from IPython.utils.traitlets import HasTraits
+from IPython.utils.tests.test_traitlets import TraitTestBase
+from IPython.html.widgets import Color
+
+
+class ColorTrait(HasTraits):
+ value = Color("black")
+
+
+class TestColor(TraitTestBase):
+ obj = ColorTrait()
+
+ _good_values = ["blue", "#AA0", "#FFFFFF"]
+ _bad_values = ["vanilla", "blues"]
diff --git a/IPython/html/widgets/trait_types.py b/IPython/html/widgets/trait_types.py
new file mode 100644
index 0000000..e09caaa
--- /dev/null
+++ b/IPython/html/widgets/trait_types.py
@@ -0,0 +1,28 @@
+# encoding: utf-8
+"""
+Trait types for html widgets.
+"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import re
+from IPython.utils import traitlets
+
+#-----------------------------------------------------------------------------
+# Utilities
+#-----------------------------------------------------------------------------
+
+_color_names = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred ', 'indigo ', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
+_color_re = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$')
+
+
+class Color(traitlets.Unicode):
+ """A string holding a valid HTML color such as 'blue', '#060482', '#A80'"""
+
+ info_text = 'a valid HTML color'
+
+ def validate(self, obj, value):
+ if value.lower() in _color_names or _color_re.match(value):
+ return value
+ self.error(obj, value)
diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py
index 50d9b90..456133d 100644
--- a/IPython/html/widgets/widget.py
+++ b/IPython/html/widgets/widget.py
@@ -22,6 +22,7 @@ from IPython.utils.importstring import import_item
from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
CaselessStrEnum, Tuple, CUnicode, Int, Set
from IPython.utils.py3compat import string_types
+from .trait_types import Color
#-----------------------------------------------------------------------------
# Classes
@@ -438,9 +439,9 @@ class DOMWidget(Widget):
padding = CUnicode(sync=True)
margin = CUnicode(sync=True)
- color = Unicode(sync=True)
- background_color = Unicode(sync=True)
- border_color = Unicode(sync=True)
+ color = Color(None, allow_none=True, sync=True)
+ background_color = Color(None, allow_none=True, sync=True)
+ border_color = Color(None, allow_none=True, sync=True)
border_width = CUnicode(sync=True)
border_radius = CUnicode(sync=True)
diff --git a/IPython/html/widgets/widget_float.py b/IPython/html/widgets/widget_float.py
index 47f6951..afc7936 100644
--- a/IPython/html/widgets/widget_float.py
+++ b/IPython/html/widgets/widget_float.py
@@ -14,6 +14,7 @@ Represents an unbounded float using a widget.
# Imports
#-----------------------------------------------------------------------------
from .widget import DOMWidget, register
+from .trait_types import Color
from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, Tuple
from IPython.utils.warn import DeprecatedClass
@@ -133,7 +134,7 @@ class FloatSlider(_BoundedFloat):
default_value='horizontal', help="Vertical or horizontal.", sync=True)
_range = Bool(False, help="Display a range selector", sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
- slider_color = Unicode(sync=True)
+ slider_color = Color(None, allow_none=True, sync=True)
@register('IPython.FloatProgress')
@@ -287,7 +288,7 @@ class FloatRangeSlider(_BoundedFloatRange):
default_value='horizontal', help="Vertical or horizontal.", sync=True)
_range = Bool(True, help="Display a range selector", sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
- slider_color = Unicode(sync=True)
+ slider_color = Color(None, allow_none=True, sync=True)
# Remove in IPython 4.0
FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py
index c455615..e006054 100644
--- a/IPython/html/widgets/widget_int.py
+++ b/IPython/html/widgets/widget_int.py
@@ -14,6 +14,7 @@ Represents an unbounded int using a widget.
# Imports
#-----------------------------------------------------------------------------
from .widget import DOMWidget, register
+from .trait_types import Color
from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, Tuple
from IPython.utils.warn import DeprecatedClass
@@ -87,7 +88,7 @@ class IntSlider(_BoundedInt):
default_value='horizontal', help="Vertical or horizontal.", sync=True)
_range = Bool(False, help="Display a range selector", sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
- slider_color = Unicode(sync=True)
+ slider_color = Color(None, allow_none=True, sync=True)
@register('IPython.IntProgress')
@@ -198,7 +199,7 @@ class IntRangeSlider(_BoundedIntRange):
default_value='horizontal', help="Vertical or horizontal.", sync=True)
_range = Bool(True, help="Display a range selector", sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
- slider_color = Unicode(sync=True)
+ slider_color = Color(None, allow_none=True, sync=True)
# Remove in IPython 4.0
IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py
index 4bb70cb..30d82a9 100644
--- a/IPython/utils/tests/test_traitlets.py
+++ b/IPython/utils/tests/test_traitlets.py
@@ -919,7 +919,6 @@ class TestDottedObjectName(TraitTestBase):
class TCPAddressTrait(HasTraits):
-
value = TCPAddress()
class TestTCPAddress(TraitTestBase):
@@ -979,6 +978,19 @@ class TestInstanceList(TraitTestBase):
_good_values = [[Foo(), Foo(), None], []]
_bad_values = [['1', 2,], '1', [Foo], None]
+class UnionListTrait(HasTraits):
+
+ value = List(Int() | Bool())
+
+class TestUnionListTrait(HasTraits):
+
+ obj = UnionListTrait()
+
+ _default_value = []
+ _good_values = [[True, 1], [False, True]]
+ _bad_values = [[1, 'True'], False]
+
+
class LenListTrait(HasTraits):
value = List(Int, [0], minlen=1, maxlen=2)