whatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinbox
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.
352 lines
10 KiB
352 lines
10 KiB
/** */ |
|
Ext.define('Ext.aria.Component', { |
|
override: 'Ext.Component', |
|
|
|
requires: [ |
|
'Ext.aria.FocusManager' |
|
], |
|
|
|
/** |
|
* @cfg {String} [ariaRole] ARIA role for this Component, defaults to no role. |
|
* With no role, no other ARIA attributes are set. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} [ariaLabel] ARIA label for this Component. It is best to use |
|
* {@link #ariaLabelledBy} option instead, because screen readers prefer |
|
* aria-labelledby attribute to aria-label. {@link #ariaLabel} and {@link #ariaLabelledBy} |
|
* config options are mutually exclusive. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} [ariaLabelledBy] DOM selector for a child element that is to be used |
|
* as label for this Component, set in aria-labelledby attribute. |
|
* If the selector is by #id, the label element can be any existing element, |
|
* not necessarily a child of the main Component element. |
|
* |
|
* {@link #ariaLabelledBy} and {@link #ariaLabel} config options are mutually exclusive, |
|
* and ariaLabel has the higher precedence. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} [ariaDescribedBy] DOM selector for a child element that is to be used |
|
* as description for this Component, set in aria-describedby attribute. |
|
* The selector works the same way as {@link #ariaLabelledBy} |
|
*/ |
|
|
|
/** |
|
* @cfg {Object} [ariaAttributes] An object containing ARIA attributes to be set |
|
* on this Component's ARIA element. Use this to set the attributes that cannot be |
|
* determined by the Component's state, such as `aria-live`, `aria-flowto`, etc. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} [ariaRenderAttributesToElement=true] ARIA attributes are usually |
|
* rendered into the main element of the Component using autoEl config option. |
|
* However for certain Components (form fields, etc.) the main element is |
|
* presentational and ARIA attributes should be rendered into child elements |
|
* of the Component markup; this is done using the Component templates. |
|
* |
|
* If this flag is set to `true` (default), the ARIA attributes will be applied |
|
* to the main element. |
|
* @private |
|
*/ |
|
ariaRenderAttributesToElement: true, |
|
|
|
statics: { |
|
ariaHighContrastModeCls: Ext.baseCSSPrefix + 'aria-highcontrast' |
|
}, |
|
|
|
// Several of the attributes, like aria-controls and aria-activedescendant |
|
// need to refer to element ids which are not available at render time |
|
ariaApplyAfterRenderAttributes: function() { |
|
var me = this, |
|
role = me.ariaRole, |
|
attrs; |
|
|
|
if (role !== 'presentation') { |
|
attrs = me.ariaGetAfterRenderAttributes(); |
|
me.ariaUpdate(attrs); |
|
} |
|
}, |
|
|
|
ariaGetRenderAttributes: function() { |
|
var me = this, |
|
role = me.ariaRole, |
|
attrs = { |
|
role: role |
|
}; |
|
|
|
// It does not make much sense to set ARIA attributes |
|
// on purely presentational Component, or on a Component |
|
// with no ARIA role specified |
|
if (role === 'presentation' || role === undefined) { |
|
return attrs; |
|
} |
|
|
|
if (me.hidden) { |
|
attrs['aria-hidden'] = true; |
|
} |
|
|
|
if (me.disabled) { |
|
attrs['aria-disabled'] = true; |
|
} |
|
|
|
if (me.ariaLabel) { |
|
attrs['aria-label'] = me.ariaLabel; |
|
} |
|
|
|
Ext.apply(attrs, me.ariaAttributes); |
|
|
|
return attrs; |
|
}, |
|
|
|
ariaGetAfterRenderAttributes: function() { |
|
var me = this, |
|
attrs = {}, |
|
el; |
|
|
|
if (!me.ariaLabel && me.ariaLabelledBy) { |
|
el = me.ariaGetLabelEl(me.ariaLabelledBy); |
|
|
|
if (el) { |
|
attrs['aria-labelledby'] = el.id; |
|
} |
|
} |
|
|
|
if (me.ariaDescribedBy) { |
|
el = me.ariaGetLabelEl(me.ariaDescribedBy); |
|
|
|
if (el) { |
|
attrs['aria-describedby'] = el.id; |
|
} |
|
} |
|
|
|
return attrs; |
|
}, |
|
|
|
/** |
|
* Updates the component's element properties |
|
* @private |
|
* @param {Ext.Element} [el] The element to set properties on |
|
* @param {Object[]} props Array of properties (name: value) |
|
*/ |
|
ariaUpdate: function(el, props) { |
|
// The one argument form updates the default ariaEl |
|
if (arguments.length === 1) { |
|
props = el; |
|
el = this.ariaGetEl(); |
|
} |
|
|
|
if (!el) { |
|
return; |
|
} |
|
|
|
el.set(props); |
|
}, |
|
|
|
/** |
|
* Return default ARIA element for this Component |
|
* @private |
|
* @return {Ext.Element} ARIA element |
|
*/ |
|
ariaGetEl: function() { |
|
return this.el; |
|
}, |
|
|
|
/** |
|
* @private |
|
* Return default ARIA labelled-by element for this Component, if any |
|
* |
|
* @param {String} [selector] Element selector |
|
* |
|
* @return {Ext.Element} Label element, or null |
|
*/ |
|
ariaGetLabelEl: function(selector) { |
|
var me = this, |
|
el = null; |
|
|
|
if (selector) { |
|
if (/^#/.test(selector)) { |
|
selector = selector.replace(/^#/, ''); |
|
el = Ext.get(selector); |
|
} |
|
else { |
|
el = me.ariaGetEl().down(selector); |
|
} |
|
} |
|
|
|
return el; |
|
}, |
|
|
|
// Unlike getFocusEl, this one always returns Ext.Element |
|
ariaGetFocusEl: function() { |
|
var el = this.getFocusEl(); |
|
|
|
while (el.isComponent) { |
|
el = el.getFocusEl(); |
|
} |
|
|
|
return el; |
|
}, |
|
|
|
onFocus: function(e, t, eOpts) { |
|
var me = this, |
|
mgr = Ext.aria.FocusManager, |
|
tip, el; |
|
|
|
me.callParent(arguments); |
|
|
|
if (me.tooltip && Ext.quickTipsActive) { |
|
tip = Ext.tip.QuickTipManager.getQuickTip(); |
|
el = me.ariaGetEl(); |
|
|
|
tip.cancelShow(el); |
|
tip.showByTarget(el); |
|
} |
|
|
|
if (me.hasFocus && mgr.enabled) { |
|
return mgr.onComponentFocus(me); |
|
} |
|
}, |
|
|
|
onBlur: function(e, t, eOpts) { |
|
var me = this, |
|
mgr = Ext.aria.FocusManager; |
|
|
|
me.callParent(arguments); |
|
|
|
if (me.tooltip && Ext.quickTipsActive) { |
|
Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.ariaGetEl()); |
|
} |
|
|
|
if (!me.hasFocus && mgr.enabled) { |
|
return mgr.onComponentBlur(me); |
|
} |
|
}, |
|
|
|
onDisable: function() { |
|
var me = this; |
|
|
|
me.callParent(arguments); |
|
me.ariaUpdate({ 'aria-disabled': true }); |
|
}, |
|
|
|
onEnable: function() { |
|
var me = this; |
|
|
|
me.callParent(arguments); |
|
me.ariaUpdate({ 'aria-disabled': false }); |
|
}, |
|
|
|
onHide: function() { |
|
var me = this; |
|
|
|
me.callParent(arguments); |
|
me.ariaUpdate({ 'aria-hidden': true }); |
|
}, |
|
|
|
onShow: function() { |
|
var me = this; |
|
|
|
me.callParent(arguments); |
|
me.ariaUpdate({ 'aria-hidden': false }); |
|
} |
|
}, |
|
function() { |
|
function detectHighContrastMode() { |
|
/* Absolute URL for test image |
|
* (data URIs are not supported by all browsers, and not properly removed when images are disabled in Firefox) */ |
|
var imgSrc = "http://www.html5accessibility.com/tests/clear.gif", |
|
supports = {}, |
|
div = document.createElement("div"), |
|
divEl = Ext.get(div), |
|
divStyle = div.style, |
|
img = document.createElement("img"), |
|
supports = { |
|
images: true, |
|
backgroundImages: true, |
|
borderColors: true, |
|
highContrastMode: false, |
|
lightOnDark: false |
|
}; |
|
|
|
/* create div for testing if high contrast mode is on or images are turned off */ |
|
div.id = "ui-helper-high-contrast"; |
|
div.className = "ui-helper-hidden-accessible"; |
|
divStyle.borderWidth = "1px"; |
|
divStyle.borderStyle = "solid"; |
|
divStyle.borderTopColor = "#F00"; |
|
divStyle.borderRightColor = "#FF0"; |
|
divStyle.backgroundColor = "#FFF"; |
|
divStyle.width = "2px"; |
|
|
|
/* For IE, div must be wider than the image inside it when hidden off screen */ |
|
img.alt = ""; |
|
div.appendChild(img); |
|
document.body.appendChild(div); |
|
divStyle.backgroundImage = "url(" + imgSrc + ")"; |
|
|
|
img.src = imgSrc; |
|
|
|
var getColorValue = function(colorTxt) { |
|
var values = [], |
|
colorValue = 0, |
|
match; |
|
|
|
if (colorTxt.indexOf("rgb(") !== -1) { |
|
values = colorTxt.replace("rgb(", "").replace(")", "").split(", "); |
|
} |
|
else if (colorTxt.indexOf("#") !== -1) { |
|
match = colorTxt.match(colorTxt.length === 7 ? /^#(\S\S)(\S\S)(\S\S)$/ : /^#(\S)(\S)(\S)$/); |
|
|
|
if (match) { |
|
values = ["0x" + match[1], "0x" + match[2], "0x" + match[3]]; |
|
} |
|
} |
|
|
|
for (var i = 0; i < values.length; i++) { |
|
colorValue += parseInt(values[i]); |
|
} |
|
|
|
return colorValue; |
|
}; |
|
|
|
var performCheck = function(event) { |
|
var bkImg = divEl.getStyle("backgroundImage"), |
|
body = Ext.getBody(); |
|
|
|
supports.images = img.offsetWidth === 1; |
|
supports.backgroundImages = !(bkImg !== null && (bkImg === "none" || bkImg === "url(invalid-url:)")); |
|
supports.borderColors = !(divEl.getStyle("borderTopColor") === divEl.getStyle("borderRightColor")); |
|
supports.highContrastMode = !supports.images || !supports.backgroundImages; |
|
supports.lightOnDark = getColorValue(divEl.getStyle("color")) - getColorValue(divEl.getStyle("backgroundColor")) > 0; |
|
|
|
if (Ext.isIE) { |
|
div.outerHTML = ""; |
|
/* prevent mixed-content warning, see http://support.microsoft.com/kb/925014 */ |
|
} else { |
|
document.body.removeChild(div); |
|
} |
|
}; |
|
|
|
performCheck(); |
|
|
|
return supports; |
|
} |
|
|
|
Ext.enableAria = true; |
|
|
|
Ext.onReady(function() { |
|
var supports = Ext.supports, |
|
flags, div; |
|
|
|
flags = Ext.isWindows ? detectHighContrastMode() : {}; |
|
|
|
supports.HighContrastMode = !!flags.highContrastMode; |
|
|
|
if (supports.HighContrastMode) { |
|
Ext.getBody().addCls(Ext.Component.ariaHighContrastModeCls); |
|
} |
|
}); |
|
});
|
|
|