Skip to content

Commit c9a38b7

Browse files
committed
fix: bugs in siblings, slideDown, show/hide, toggle; 109 tests, 98.9% coverage; README fixes
1 parent 9ebfe08 commit c9a38b7

10 files changed

Lines changed: 1410 additions & 228 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [2.0.1]
6+
7+
### Fixed
8+
- `siblings()` no longer throws on detached elements (null parentNode).
9+
- `slideDown()` no longer forces `display: block`; restores original non-`none` inline display after animation.
10+
- `show()` now restores the original inline display value that was set before `hide()`, using an internal `WeakMap`.
11+
- `toggle()` now checks `getComputedStyle` instead of only inline `style.display`, correctly handling CSS-hidden elements.
12+
- README: fixed outdated/incorrect statements (`$(document).ready`, delegation limitations, single-root HTML).
13+
- Test suite expanded from 24 to 109 tests (98.9% statement coverage, 100% function coverage).
14+
515
## [2.0.0]
616

717
### Breaking

GStime.js

Lines changed: 89 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
const HTML_RE = /^\s*<([a-z][\w-]*)[\s\S]*>/i;
2424
// WeakMap<Element, Map<event, Array<{ user, listener, selector }>>>
2525
const listenerStore = isClient ? new WeakMap() : null;
26+
// WeakMap<Element, string> — stores original display value before hide()
27+
const displayStore = isClient ? new WeakMap() : null;
2628

2729
function getListenerMap(el, event) {
2830
let map = listenerStore.get(el);
@@ -237,24 +239,42 @@
237239
* @returns {GStime} The GStime object for chaining.
238240
*/
239241
GStime.prototype.hide = function () {
240-
return this.css("display", "none");
242+
return this.each(function () {
243+
if (this.nodeType === 1) {
244+
const current = this.style.display;
245+
if (current !== "none") {
246+
displayStore.set(this, current);
247+
}
248+
this.style.display = "none";
249+
}
250+
});
241251
};
242-
252+
243253
/**
244254
* Displays the matched elements.
245255
* @returns {GStime} The GStime object for chaining.
246256
*/
247257
GStime.prototype.show = function () {
248-
return this.css("display", "");
258+
return this.each(function () {
259+
if (this.nodeType === 1) {
260+
const prev = displayStore.get(this);
261+
this.style.display = prev || "";
262+
displayStore.delete(this);
263+
}
264+
});
249265
};
250-
266+
251267
/**
252268
* Toggles the visibility of the matched elements.
253269
* @returns {GStime} The GStime object for chaining.
254270
*/
255271
GStime.prototype.toggle = function () {
256272
return this.each(function () {
257-
this.style.display = this.style.display === "none" ? "" : "none";
273+
if (getComputedStyle(this).display === "none") {
274+
new GStime(this).show();
275+
} else {
276+
new GStime(this).hide();
277+
}
258278
});
259279
};
260280

@@ -703,12 +723,14 @@
703723
* Gets the siblings of each element in the set of matched elements.
704724
* @returns {GStime} A new GStime object containing the sibling elements.
705725
*/
706-
GStime.prototype.siblings = function () {
707-
const siblings = this.elements.flatMap((el) =>
708-
Array.from(el.parentNode.children).filter((child) => child !== el)
709-
);
710-
return new GStime(siblings);
711-
};
726+
GStime.prototype.siblings = function () {
727+
const siblings = this.elements.flatMap((el) =>
728+
el.parentNode
729+
? Array.from(el.parentNode.children).filter((child) => child !== el)
730+
: []
731+
);
732+
return new GStime(siblings);
733+
};
712734

713735
/**
714736
* Finds descendants of each element in the current set of matched elements.
@@ -849,52 +871,64 @@
849871
* @param {function} [callback] - A function to call once the animation is complete.
850872
* @returns {GStime} The GStime object for chaining.
851873
*/
852-
GStime.prototype.slideDown = function (duration, callback) {
853-
return this.each(function () {
854-
const el = this;
855-
const prevDisplay = el.style.display;
856-
857-
// Make sure the element is visible but with zero height
858-
el.style.overflow = 'hidden';
859-
if (getComputedStyle(el).display === 'none') el.style.display = 'block';
860-
el.style.height = '0px';
861-
el.style.paddingTop = '0px';
862-
el.style.paddingBottom = '0px';
863-
el.style.marginTop = '0px';
864-
el.style.marginBottom = '0px';
865-
866-
// Get natural height
867-
const targetHeight = el.scrollHeight;
868-
869-
// Start animation
870-
requestAnimationFrame(() => {
871-
el.style.transition = `height ${duration}ms, padding ${duration}ms, margin ${duration}ms`;
872-
el.style.height = `${targetHeight}px`;
873-
el.style.paddingTop = '';
874-
el.style.paddingBottom = '';
875-
el.style.marginTop = '';
876-
el.style.marginBottom = '';
874+
GStime.prototype.slideDown = function (duration, callback) {
875+
return this.each(function () {
876+
const el = this;
877+
const wasHidden = getComputedStyle(el).display === 'none';
877878

878-
let done = false;
879-
const finish = function () {
880-
if (done) return;
881-
done = true;
882-
el.style.height = '';
883-
el.style.overflow = '';
884-
el.style.transition = '';
885-
if (prevDisplay) el.style.display = prevDisplay;
886-
el.removeEventListener('transitionend', onTransitionEnd);
887-
if (typeof callback === 'function') callback.call(el);
888-
};
889-
const onTransitionEnd = function (e) {
890-
if (e.target !== el || e.propertyName !== 'height') return;
891-
finish();
892-
};
893-
el.addEventListener('transitionend', onTransitionEnd);
894-
setTimeout(finish, duration + 50);
879+
// Store original inline display; if it is "none" we should not restore it
880+
const prevDisplay = el.style.display && el.style.display !== 'none'
881+
? el.style.display
882+
: '';
883+
884+
el.style.overflow = 'hidden';
885+
if (wasHidden) {
886+
el.style.display = '';
887+
}
888+
// If still hidden after clearing inline style, force block
889+
if (getComputedStyle(el).display === 'none') {
890+
el.style.display = 'block';
891+
}
892+
el.style.height = '0px';
893+
el.style.paddingTop = '0px';
894+
el.style.paddingBottom = '0px';
895+
el.style.marginTop = '0px';
896+
el.style.marginBottom = '0px';
897+
898+
// Get natural height
899+
const targetHeight = el.scrollHeight;
900+
901+
// Start animation
902+
requestAnimationFrame(() => {
903+
el.style.transition = `height ${duration}ms, padding ${duration}ms, margin ${duration}ms`;
904+
el.style.height = `${targetHeight}px`;
905+
el.style.paddingTop = '';
906+
el.style.paddingBottom = '';
907+
el.style.marginTop = '';
908+
el.style.marginBottom = '';
909+
910+
let done = false;
911+
const finish = function () {
912+
if (done) return;
913+
done = true;
914+
el.style.height = '';
915+
el.style.overflow = '';
916+
el.style.transition = '';
917+
if (wasHidden && prevDisplay) {
918+
el.style.display = prevDisplay;
919+
}
920+
el.removeEventListener('transitionend', onTransitionEnd);
921+
if (typeof callback === 'function') callback.call(el);
922+
};
923+
const onTransitionEnd = function (e) {
924+
if (e.target !== el || e.propertyName !== 'height') return;
925+
finish();
926+
};
927+
el.addEventListener('transitionend', onTransitionEnd);
928+
setTimeout(finish, duration + 50);
929+
});
895930
});
896-
});
897-
};
931+
};
898932

899933
/**
900934
* Toggles between slideUp and slideDown for the matched elements.

GStime.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

GStime.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ GStime.js is a lightweight JavaScript library for DOM manipulation, animation cr
5454

5555
<script>
5656
// DOM ready
57-
$(document).ready(function() {
58-
// Select elements and manipulate them
59-
$('button').on('click', function() {
60-
$('.box').fadeIn(500);
61-
});
57+
$(function() {
58+
// Select elements and manipulate them
59+
$('button').on('click', function() {
60+
$('.box').fadeIn(500);
6261
});
62+
});
6363
</script>
6464
```
6565

@@ -189,9 +189,6 @@ Creates a new GStime object with elements matching the selector.
189189
**Parameters:**
190190
- `selector` (String|Element|NodeList|Array): CSS selector, HTML string, or DOM element.
191191

192-
**Limitations:**
193-
- When passing an HTML string, only one top-level element is created.
194-
195192
#### `.each(callback)`
196193

197194
Executes a function for each element in the set.
@@ -208,19 +205,13 @@ Attaches an event handler to elements.
208205
- `selector` (String, optional): Selector for event delegation.
209206
- `callback` (Function): Event handler function.
210207

211-
**Limitations:**
212-
- Delegation only works for elements that exist at the time the event is bound.
213-
214208
#### `.off(event, callback)`
215209

216210
Removes an event handler from elements.
217211

218212
**Parameters:**
219213
- `event` (String): Name of the event.
220-
- `callback` (Function): Function to remove.
221-
222-
**Limitations:**
223-
- When using delegated events, you must pass exactly the same function that was assigned via `.on()`.
214+
- `callback` (Function): Function to remove. If omitted, removes all handlers for the event.
224215

225216
#### `.val([newVal])`
226217

README_ru.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ GStime.js - это легковесная JavaScript библиотека для
5050

5151
<script>
5252
// DOM готов
53-
$(document).ready(function() {
53+
$(function() {
5454
// Выбор элементов и манипуляция с ними
5555
$('button').on('click', function() {
5656
$('.box').fadeIn(500);

0 commit comments

Comments
 (0)