forked from mathiasbynens/jquery-details
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jquery.details.js
161 lines (139 loc) · 5.84 KB
/
jquery.details.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*! http://mths.be/details v0.1.0 by @mathias | includes http://mths.be/noselect v1.0.3 */
;(function(document, $) {
var proto = $.fn,
details,
// :'(
isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]',
// Feature test for native `<details>` support
isDetailsSupported = (function(doc) {
var el = doc.createElement('details'),
fake,
root,
diff;
if (!('open' in el)) {
return false;
}
root = doc.body || (function() {
var de = doc.documentElement;
fake = true;
return de.insertBefore(doc.createElement('body'), de.firstElementChild || de.firstChild);
}());
el.innerHTML = '<summary>a</summary>b';
el.style.display = 'block';
root.appendChild(el);
diff = el.offsetHeight;
el.open = true;
diff = diff != el.offsetHeight;
root.removeChild(el);
if (fake) {
root.parentNode.removeChild(root);
}
return diff;
}(document)),
toggleOpen = function($details, $detailsSummary, $detailsNotSummary, toggle) {
var isOpen = $details.prop('open'),
close = isOpen && toggle || !isOpen && !toggle;
if (close) {
$details.removeClass('open').prop('open', false).triggerHandler('close.details');
$detailsSummary.attr('aria-expanded', false);
$detailsNotSummary.hide();
} else {
$details.addClass('open').prop('open', true).triggerHandler('open.details');
$detailsSummary.attr('aria-expanded', true);
$detailsNotSummary.show();
}
};
/* http://mths.be/noselect v1.0.3 */
proto.noSelect = function() {
// Since the string 'none' is used three times, storing it in a variable gives better results after minification
var none = 'none';
// onselectstart and ondragstart for WebKit & IE
// onmousedown for WebKit & Opera
return this.bind('selectstart dragstart mousedown', function() {
return false;
}).css({
'MozUserSelect': none,
'msUserSelect': none,
'webkitUserSelect': none,
'userSelect': none
});
};
// Execute the fallback only if there’s no native `details` support
if (isDetailsSupported) {
details = proto.details = function() {
return this.each(function(i) {
var $details = $(this),
$summary = $('summary', $details).first();
//if $details doesn't already one, assign a generated @id for @aria-controls on the summary to reference
if (!$details.attr('id')) {
$details.attr('id', 'details-id-' + i);
}
$details.attr('role', 'group');
$summary.attr({
'role': 'button',
'aria-expanded': $details.prop('open'),
'aria-controls': $details.attr('id')
}).on('click', function() {
// the value of the `open` property is the old value
var close = $details.prop('open');
$summary.attr('aria-expanded', !close);
$details.triggerHandler((close ? 'close' : 'open') + '.details');
});
});
};
details.support = isDetailsSupported;
} else {
details = proto.details = function() {
// Loop through all `details` elements
return this.each(function(i) {
// Store a reference to the current `details` element in a variable
var $details = $(this),
// Store a reference to the `summary` element of the current `details` element (if any) in a variable
$detailsSummary = $('summary', $details).first(),
// Do the same for the info within the `details` element
$detailsNotSummary = $details.children(':not(summary)'),
// This will be used later to look for direct child text nodes
$detailsNotSummaryContents = $details.contents(':not(summary)');
//assign a generated @id to the details element for reference by @aria-controls on the summary
if (!$details.attr('id')) {
$details.attr('id', 'details-id-' + i);
}
$details.attr('role', 'group');
// If there is no `summary` in the current `details` element…
if (!$detailsSummary.length) {
// …create one with default text
$detailsSummary = $('<summary>').text('Details').prependTo($details);
}
// Look for direct child text nodes
if ($detailsNotSummary.length != $detailsNotSummaryContents.length) {
// Wrap child text nodes in a `span` element
$detailsNotSummaryContents.filter(function() {
// Only keep the node in the collection if it’s a text node containing more than only whitespace
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#space-character
return this.nodeType == 3 && /[^ \t\n\f\r]/.test(this.data);
}).wrap('<span>');
// There are now no direct child text nodes anymore — they’re wrapped in `span` elements
$detailsNotSummary = $details.children(':not(summary)');
}
// Hide content unless there’s an `open` attribute
$details.prop('open', typeof $details.attr('open') == 'string');
toggleOpen($details, $detailsSummary, $detailsNotSummary);
// Add `role=button` and set the `tabindex` of the `summary` element to `0` to make it keyboard accessible
$detailsSummary.attr({'role': 'button', 'aria-controls': $details.attr('id')}).noSelect().prop('tabIndex', 0).on('click', function() {
// Focus on the `summary` element
$detailsSummary.focus();
// Toggle the `open` and `aria-expanded` attributes and the `open` property of the `details` element and display the additional info
toggleOpen($details, $detailsSummary, $detailsNotSummary, true);
}).keyup(function(event) {
if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) {
// Space or Enter is pressed — trigger the `click` event on the `summary` element
// Opera already seems to trigger the `click` event when Enter is pressed
event.preventDefault();
$detailsSummary.click();
}
});
});
};
details.support = isDetailsSupported;
}
}(document, jQuery));