You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
12 KiB
372 lines
12 KiB
define([ |
|
"dojo/_base/array", // array.forEach array.map |
|
"dojo/_base/declare", // declare |
|
"dojo/dom", // dom.isDescendant() |
|
"dojo/dom-class", // domClass.toggle |
|
"dojo/has", |
|
"dojo/_base/lang", // lang.hitch |
|
"dojo/on", |
|
"dojo/domReady", |
|
"dojo/touch", |
|
"dojo/_base/window", // win.body |
|
"./a11yclick", |
|
"./registry" |
|
], function(array, declare, dom, domClass, has, lang, on, domReady, touch, win, a11yclick, registry){ |
|
|
|
// module: |
|
// dijit/_CssStateMixin |
|
|
|
var CssStateMixin = declare("dijit._CssStateMixin", [], { |
|
// summary: |
|
// Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus |
|
// state changes, and also higher-level state changes such becoming disabled or selected. |
|
// |
|
// description: |
|
// By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically |
|
// maintain CSS classes on the widget root node (this.domNode) depending on hover, |
|
// active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes |
|
// dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it. |
|
// |
|
// It also sets CSS like dijitButtonDisabled based on widget semantic state. |
|
// |
|
// By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons |
|
// within the widget). |
|
|
|
/*===== |
|
// cssStateNodes: [protected] Object |
|
// Subclasses may define a cssStateNodes property that lists sub-nodes within the widget that |
|
// need CSS classes applied on mouse hover/press and focus. |
|
// |
|
// Each entry in this optional hash is a an attach-point name (like "upArrowButton") mapped to a CSS class name |
|
// (like "dijitUpArrowButton"). Example: |
|
// | { |
|
// | "upArrowButton": "dijitUpArrowButton", |
|
// | "downArrowButton": "dijitDownArrowButton" |
|
// | } |
|
// The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it |
|
// is hovered, etc. |
|
cssStateNodes: {}, |
|
=====*/ |
|
|
|
// hovering: [readonly] Boolean |
|
// True if cursor is over this widget |
|
hovering: false, |
|
|
|
// active: [readonly] Boolean |
|
// True if mouse was pressed while over this widget, and hasn't been released yet |
|
active: false, |
|
|
|
_applyAttributes: function(){ |
|
// This code would typically be in postCreate(), but putting in _applyAttributes() for |
|
// performance: so the class changes happen before DOM is inserted into the document. |
|
// Change back to postCreate() in 2.0. See #11635. |
|
|
|
this.inherited(arguments); |
|
|
|
// Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node |
|
array.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active", "_opened"], function(attr){ |
|
this.watch(attr, lang.hitch(this, "_setStateClass")); |
|
}, this); |
|
|
|
// Track hover and active mouse events on widget root node, plus possibly on subnodes |
|
for(var ap in this.cssStateNodes || {}){ |
|
this._trackMouseState(this[ap], this.cssStateNodes[ap]); |
|
} |
|
this._trackMouseState(this.domNode, this.baseClass); |
|
|
|
// Set state initially; there's probably no hover/active/focus state but widget might be |
|
// disabled/readonly/checked/selected so we want to set CSS classes for those conditions. |
|
this._setStateClass(); |
|
}, |
|
|
|
_cssMouseEvent: function(/*Event*/ event){ |
|
// summary: |
|
// Handler for CSS event on this.domNode. Sets hovering and active properties depending on mouse state, |
|
// which triggers _setStateClass() to set appropriate CSS classes for this.domNode. |
|
|
|
if(!this.disabled){ |
|
switch(event.type){ |
|
case "mouseover": |
|
case "MSPointerOver": |
|
case "pointerover": |
|
this._set("hovering", true); |
|
this._set("active", this._mouseDown); |
|
break; |
|
case "mouseout": |
|
case "MSPointerOut": |
|
case "pointerout": |
|
this._set("hovering", false); |
|
this._set("active", false); |
|
break; |
|
case "mousedown": |
|
case "touchstart": |
|
case "MSPointerDown": |
|
case "pointerdown": |
|
case "keydown": |
|
this._set("active", true); |
|
break; |
|
case "mouseup": |
|
case "dojotouchend": |
|
case "MSPointerUp": |
|
case "pointerup": |
|
case "keyup": |
|
this._set("active", false); |
|
break; |
|
} |
|
} |
|
}, |
|
|
|
_setStateClass: function(){ |
|
// summary: |
|
// Update the visual state of the widget by setting the css classes on this.domNode |
|
// (or this.stateNode if defined) by combining this.baseClass with |
|
// various suffixes that represent the current widget state(s). |
|
// |
|
// description: |
|
// In the case where a widget has multiple |
|
// states, it sets the class based on all possible |
|
// combinations. For example, an invalid form widget that is being hovered |
|
// will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover". |
|
// |
|
// The widget may have one or more of the following states, determined |
|
// by this.state, this.checked, this.valid, and this.selected: |
|
// |
|
// - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid |
|
// - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet |
|
// - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true |
|
// - Selected - ex: currently selected tab will have this.selected==true |
|
// |
|
// In addition, it may have one or more of the following states, |
|
// based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused): |
|
// |
|
// - Disabled - if the widget is disabled |
|
// - Active - if the mouse (or space/enter key?) is being pressed down |
|
// - Focused - if the widget has focus |
|
// - Hover - if the mouse is over the widget |
|
|
|
// Compute new set of classes |
|
var newStateClasses = this.baseClass.split(" "); |
|
|
|
function multiply(modifier){ |
|
newStateClasses = newStateClasses.concat(array.map(newStateClasses, function(c){ |
|
return c + modifier; |
|
}), "dijit" + modifier); |
|
} |
|
|
|
if(!this.isLeftToRight()){ |
|
// For RTL mode we need to set an addition class like dijitTextBoxRtl. |
|
multiply("Rtl"); |
|
} |
|
|
|
var checkedState = this.checked == "mixed" ? "Mixed" : (this.checked ? "Checked" : ""); |
|
if(this.checked){ |
|
multiply(checkedState); |
|
} |
|
if(this.state){ |
|
multiply(this.state); |
|
} |
|
if(this.selected){ |
|
multiply("Selected"); |
|
} |
|
if(this._opened){ |
|
multiply("Opened"); |
|
} |
|
|
|
if(this.disabled){ |
|
multiply("Disabled"); |
|
}else if(this.readOnly){ |
|
multiply("ReadOnly"); |
|
}else{ |
|
if(this.active){ |
|
multiply("Active"); |
|
}else if(this.hovering){ |
|
multiply("Hover"); |
|
} |
|
} |
|
|
|
if(this.focused){ |
|
multiply("Focused"); |
|
} |
|
|
|
// Remove old state classes and add new ones. |
|
// For performance concerns we only write into domNode.className once. |
|
var tn = this.stateNode || this.domNode, |
|
classHash = {}; // set of all classes (state and otherwise) for node |
|
|
|
array.forEach(tn.className.split(" "), function(c){ |
|
classHash[c] = true; |
|
}); |
|
|
|
if("_stateClasses" in this){ |
|
array.forEach(this._stateClasses, function(c){ |
|
delete classHash[c]; |
|
}); |
|
} |
|
|
|
array.forEach(newStateClasses, function(c){ |
|
classHash[c] = true; |
|
}); |
|
|
|
var newClasses = []; |
|
for(var c in classHash){ |
|
newClasses.push(c); |
|
} |
|
tn.className = newClasses.join(" "); |
|
|
|
this._stateClasses = newStateClasses; |
|
}, |
|
|
|
_subnodeCssMouseEvent: function(node, clazz, evt){ |
|
// summary: |
|
// Handler for hover/active mouse event on widget's subnode |
|
if(this.disabled || this.readOnly){ |
|
return; |
|
} |
|
|
|
function hover(isHovering){ |
|
domClass.toggle(node, clazz + "Hover", isHovering); |
|
} |
|
|
|
function active(isActive){ |
|
domClass.toggle(node, clazz + "Active", isActive); |
|
} |
|
|
|
function focused(isFocused){ |
|
domClass.toggle(node, clazz + "Focused", isFocused); |
|
} |
|
|
|
switch(evt.type){ |
|
case "mouseover": |
|
case "MSPointerOver": |
|
case "pointerover": |
|
hover(true); |
|
break; |
|
case "mouseout": |
|
case "MSPointerOut": |
|
case "pointerout": |
|
hover(false); |
|
active(false); |
|
break; |
|
case "mousedown": |
|
case "touchstart": |
|
case "MSPointerDown": |
|
case "pointerdown": |
|
case "keydown": |
|
active(true); |
|
break; |
|
case "mouseup": |
|
case "MSPointerUp": |
|
case "pointerup": |
|
case "dojotouchend": |
|
case "keyup": |
|
active(false); |
|
break; |
|
case "focus": |
|
case "focusin": |
|
focused(true); |
|
break; |
|
case "blur": |
|
case "focusout": |
|
focused(false); |
|
break; |
|
} |
|
}, |
|
|
|
_trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){ |
|
// summary: |
|
// Track mouse/focus events on specified node and set CSS class on that node to indicate |
|
// current state. Usually not called directly, but via cssStateNodes attribute. |
|
// description: |
|
// Given class=foo, will set the following CSS class on the node |
|
// |
|
// - fooActive: if the user is currently pressing down the mouse button while over the node |
|
// - fooHover: if the user is hovering the mouse over the node, but not pressing down a button |
|
// - fooFocus: if the node is focused |
|
// |
|
// Note that it won't set any classes if the widget is disabled. |
|
// node: DomNode |
|
// Should be a sub-node of the widget, not the top node (this.domNode), since the top node |
|
// is handled specially and automatically just by mixing in this class. |
|
// clazz: String |
|
// CSS class name (ex: dijitSliderUpArrow) |
|
|
|
// Flag for listener code below to call this._cssMouseEvent() or this._subnodeCssMouseEvent() |
|
// when node is hovered/active |
|
node._cssState = clazz; |
|
} |
|
}); |
|
|
|
domReady(function(){ |
|
// Document level listener to catch hover etc. events on widget root nodes and subnodes. |
|
// Note that when the mouse is moved quickly, a single onmouseenter event could signal that multiple widgets |
|
// have been hovered or unhovered (try test_Accordion.html) |
|
|
|
function pointerHandler(evt, target, relatedTarget){ |
|
// Handler for mouseover, mouseout, a11yclick.press and a11click.release events |
|
|
|
// Poor man's event propagation. Don't propagate event to ancestors of evt.relatedTarget, |
|
// to avoid processing mouseout events moving from a widget's domNode to a descendant node; |
|
// such events shouldn't be interpreted as a mouseleave on the widget. |
|
if(relatedTarget && dom.isDescendant(relatedTarget, target)){ |
|
return; |
|
} |
|
|
|
for(var node = target; node && node != relatedTarget; node = node.parentNode){ |
|
// Process any nodes with _cssState property. They are generally widget root nodes, |
|
// but could also be sub-nodes within a widget |
|
if(node._cssState){ |
|
var widget = registry.getEnclosingWidget(node); |
|
if(widget){ |
|
if(node == widget.domNode){ |
|
// event on the widget's root node |
|
widget._cssMouseEvent(evt); |
|
}else{ |
|
// event on widget's sub-node |
|
widget._subnodeCssMouseEvent(node, node._cssState, evt); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
var body = win.body(), activeNode; |
|
|
|
// Handle pointer related events (i.e. mouse or touch) |
|
on(body, touch.over, function(evt){ |
|
// Using touch.over rather than mouseover mainly to ignore phantom mouse events on iOS. |
|
pointerHandler(evt, evt.target, evt.relatedTarget); |
|
}); |
|
on(body, touch.out, function(evt){ |
|
// Using touch.out rather than mouseout mainly to ignore phantom mouse events on iOS. |
|
pointerHandler(evt, evt.target, evt.relatedTarget); |
|
}); |
|
on(body, a11yclick.press, function(evt){ |
|
// Save the a11yclick.press target to reference when the a11yclick.release comes. |
|
activeNode = evt.target; |
|
pointerHandler(evt, activeNode) |
|
}); |
|
on(body, a11yclick.release, function(evt){ |
|
// The release event could come on a separate node than the press event, if for example user slid finger. |
|
// Reference activeNode to reset the state of the node that got state set in the a11yclick.press handler. |
|
pointerHandler(evt, activeNode); |
|
activeNode = null; |
|
}); |
|
|
|
// Track focus events on widget sub-nodes that have been registered via _trackMouseState(). |
|
// However, don't track focus events on the widget root nodes, because focus is tracked via the |
|
// focus manager (and it's not really tracking focus, but rather tracking that focus is on one of the widget's |
|
// nodes or a subwidget's node or a popup node, etc.) |
|
// Remove for 2.0 (if focus CSS needed, just use :focus pseudo-selector). |
|
on(body, "focusin, focusout", function(evt){ |
|
var node = evt.target; |
|
if(node._cssState && !node.getAttribute("widgetId")){ |
|
var widget = registry.getEnclosingWidget(node); |
|
if(widget){ |
|
widget._subnodeCssMouseEvent(node, node._cssState, evt); |
|
} |
|
} |
|
}); |
|
}); |
|
|
|
return CssStateMixin; |
|
});
|
|
|