Skip to content

Commit 731b792

Browse files
committed
merge
1 parent 26d554b commit 731b792

File tree

2 files changed

+124
-87
lines changed

2 files changed

+124
-87
lines changed

README.md

Lines changed: 105 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,103 +2,165 @@
22

33
Tiny, CDN-friendly helpers to make jQuery traversal and ergonomics snappier — no widgets, just micro-utilities.
44

5+
## Why?
6+
7+
jQuery is great, but sometimes you need:
8+
- **First match per element** (not all matches or globally first)
9+
- **Short-circuit traversal** (stop at first match, not collect all)
10+
- **Predicate functions** (not just CSS selectors)
11+
- **Modern conveniences** (tap, viewport detection)
12+
13+
This adds ~1KB of focused utilities without the bloat of jQuery UI or larger plugins.
14+
515
## Install
616

717
```html
818
<script src="https://code.jquery.com/jquery-3.7.1.slim.min.js"></script>
9-
<script src="https://cdn.jsdelivr.net/gh/AnswerDotAI/jquery-micro-utils/src/jquery-micro-utils.js"></script>
19+
<script src="https://cdn.jsdelivr.net/gh/AnswerDotAI/jquery-micro-utils@0.1.2/src/jquery-micro-utils.js"></script>
1020
```
1121

12-
UMD build works with `<script>`, AMD, or CommonJS.
22+
Or use npm/yarn:
23+
```bash
24+
npm install jquery-micro-utils
25+
```
1326

1427
## API
1528

16-
### `.nextMatch(selectorOrFn)`
29+
### `.nextMatch(selectorOrFn)` / `.prevMatch(selectorOrFn)`
1730

18-
**What:** First following sibling that matches. Short-circuits (faster than `.nextAll().first()`).
31+
Find the **first** sibling that matches (not all). Accepts CSS selector or predicate function.
1932

2033
```js
21-
const $bar = $('.foo').nextMatch('.bar');
22-
if ($bar.exists) {
23-
$bar.addClass('hit');
24-
}
25-
```
26-
27-
### `.prevMatch(selectorOrFn)`
34+
// Find next sibling with 10+ characters (can't do this with jQuery's .next())
35+
$('.item').nextMatch(el => el.textContent.length > 10);
2836

29-
**What:** First previous sibling that matches (short-circuits).
37+
// More efficient than .nextAll('.active').first() - stops at first match
38+
$('.current').nextMatch('.active').addClass('highlight');
3039

31-
```js
32-
$('#item42').prevMatch('.active').tap($x => console.log($x[0]));
40+
// Works backwards too
41+
$('#item-5').prevMatch('.section-header').css('font-weight', 'bold');
3342
```
3443

44+
**vs jQuery:** `.next()` only checks immediate sibling. `.nextAll()` gets ALL matches. `nextMatch()` finds FIRST match per element.
45+
3546
### `.findFirst(selector)`
3647

37-
**What:** First descendant under each root via `querySelector`.
48+
Get the **first descendant** from each element (not all descendants).
3849

3950
```js
40-
const $btn = $('.card').findFirst('button.primary');
51+
// Get first button from EACH card (not just the globally first)
52+
$('.card').findFirst('button').prop('disabled', true);
53+
54+
// Faster than .find('img').first() when you only need one per container
55+
$('.gallery-row').findFirst('img').addClass('hero');
4156
```
4257

58+
**vs jQuery:** `.find()` returns ALL matches. `.find().first()` returns globally first. `findFirst()` returns first match FROM EACH element.
59+
4360
### `.containsText(str|RegExp)`
4461

45-
**What:** Filter elements by `textContent` (string contains or RegExp test).
62+
Filter elements by text content.
4663

4764
```js
48-
const $matches = $('li').containsText(/done|complete/i);
65+
// String contains
66+
$('button').containsText('Save').addClass('primary');
67+
68+
// RegExp for complex matching
69+
$('tr').containsText(/^(error|warning):/i).css('color', 'red');
70+
71+
// Find elements with specific price ranges
72+
$('.price').containsText(/\$[5-9][0-9]\./).addClass('mid-range');
4973
```
5074

5175
### `.inViewport(margin = 0)`
5276

53-
**What:** Elements whose bounding rect intersects the viewport (±margin px).
77+
Filter to elements currently visible in viewport.
5478

5579
```js
56-
const $visible = $('.section').inViewport(100);
80+
// Lazy-load images as they come into view
81+
$('img[data-src]').inViewport(200).each(function() {
82+
this.src = this.dataset.src;
83+
delete this.dataset.src;
84+
});
85+
86+
// Animate elements when visible
87+
$('.animate-me').inViewport().addClass('fade-in');
88+
89+
// Check what sections user can see
90+
$('section').inViewport().tap(console.log);
5791
```
5892

5993
### `.tap(fn)`
6094

61-
**What:** Chain-tap for debugging/side-effects; returns the same jQuery set.
95+
Debug or side-effect without breaking the chain.
6296

6397
```js
64-
$('.item').tap($it => console.log('count:', $it.length)).addClass('seen');
98+
$('.item')
99+
.tap($els => console.log(`Processing ${$els.length} items`))
100+
.addClass('processed')
101+
.tap($els => analytics.track('items.processed', $els.length))
102+
.fadeIn();
103+
104+
// Quick debugging
105+
$('.mystery').tap(console.log).remove();
65106
```
66107

67108
### `$.as$(x)`
68109

69-
**What:** Normalize DOM or jQuery input to a jQuery object.
110+
Safely convert anything to jQuery object.
70111

71112
```js
72-
const $el = $.as$(maybeDomOrJq);
113+
function process(element) {
114+
// Don't worry if element is DOM node, jQuery object, or selector string
115+
const $el = $.as$(element);
116+
$el.addClass('processed');
117+
}
118+
119+
process(document.querySelector('.item')); // DOM node - works
120+
process($('.item')); // jQuery object - works
121+
process('.item'); // Selector string - works
73122
```
74123

75-
## Development
124+
## Real-World Examples
76125

77-
```bash
78-
npm i
126+
### Accordion behavior
127+
```js
128+
$('.accordion-header').on('click', function() {
129+
$(this)
130+
.nextMatch('.accordion-content')
131+
.slideToggle()
132+
.tap($content => console.log('Toggled:', $content));
133+
});
79134
```
80135

81-
### Manual Demo
136+
### Progressive enhancement
137+
```js
138+
// Add "Read more" only to truncated content
139+
$('.content').containsText('...').after('<button>Read more</button>');
140+
```
82141

83-
Open `examples/index.html` in any browser to try the utilities against a small fixture page (no build or server required). It loads jQuery 3.x from the CDN and the UMD source from `src/`.
142+
### Performance monitoring
143+
```js
144+
// Track which sections users actually see
145+
const viewed = new Set();
146+
setInterval(() => {
147+
$('section[id]').inViewport().each(function() {
148+
if (!viewed.has(this.id)) {
149+
viewed.add(this.id);
150+
analytics.track('section.viewed', { id: this.id });
151+
}
152+
});
153+
}, 1000);
154+
```
84155

85-
Or run a tiny static server:
156+
## Development
86157

87158
```bash
88-
npm run serve
159+
npm install
160+
npm run serve # localhost:8080/examples/
89161
```
90162

91-
Then open `http://localhost:8080/examples/`.
92-
93-
## Files
94-
95-
- `src/jquery-micro-utils.js` – source (UMD)
96-
- `examples/index.html` – manual browser demo
97-
98-
## Metadata
99-
100-
- `$.microUtils.version`: library version string.
101-
102163
## License
103164

104-
MIT, © Answer.AI 2025
165+
MIT © Answer.AI 2025
166+

src/jquery-micro-utils.js

Lines changed: 19 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,28 @@
1010
}(function ($) {
1111
'use strict';
1212

13-
if (!$ || !$.fn) { throw new Error('jquery-micro-utils requires jQuery to be loaded first.'); }
13+
if (!$?.fn) throw new Error('jquery-micro-utils requires jQuery to be loaded first.');
1414

1515
function toPred(test) {
1616
if (typeof test === 'function') return test;
17-
if (typeof test === 'string' && test.trim()) {
18-
return function (el) { return el.matches(test); };
19-
}
20-
return function () { return true; };
17+
if (typeof test === 'string' && test.trim()) { return el => el.matches(test); }
18+
return () => true;
2119
}
2220

21+
function toUnq(nodes) { return $($.uniqueSort($.makeArray(nodes).filter(Boolean))); }
22+
2323
function firstSibling(el, dir, pred) {
24-
while (el && (el = el[dir])) { if (pred(el)) return el; }
24+
while (el = el[dir]) { if (pred(el)) return el; }
2525
}
2626

27-
function siblingMatch(set, dir, test) {
28-
var pred = toPred(test);
29-
var $mapped = set.map(function (_i, el) { return firstSibling(el, dir, pred); });
30-
return $( $.uniqueSort($mapped.get()));
31-
}
27+
function siblingMatch(set, dir, test) { return toUnq(set.map((_, el) => firstSibling(el, dir, toPred(test)))); }
3228

3329
$.fn.nextMatch = function (selectorOrFn) { return siblingMatch(this, 'nextElementSibling', selectorOrFn); };
34-
3530
$.fn.prevMatch = function (selectorOrFn) { return siblingMatch(this, 'previousElementSibling', selectorOrFn); };
3631

37-
function toUnq(nodes) {
38-
var arr = [];
39-
for (let i = 0; i < nodes.length; i++) {
40-
var n = nodes[i];
41-
if (n) arr.push(n);
42-
}
43-
return $( $.uniqueSort(arr) );
44-
}
45-
46-
$.fn.findFirst = function (selector) {
47-
if (typeof selector !== 'string' || !selector.trim()) return this.pushStack([]);
48-
var out = new Array(this.length);
49-
for (let i = 0; i < this.length; i++) {
50-
var el = this[i];
51-
out[i] = el && el.querySelector ? el.querySelector(selector) : null;
52-
}
53-
return toUnq(out);
32+
$.fn.findFirst = function(selector) {
33+
if (!selector?.trim()) return this.pushStack([]);
34+
return toUnq(this.map((_, el) => el.querySelector?.(selector)));
5435
};
5536

5637
$.fn.containsText = function (query) {
@@ -63,30 +44,24 @@
6344
};
6445

6546
$.fn.tap = function (fn) {
66-
if (typeof fn === 'function') fn(this);
47+
fn?.(this);
6748
return this;
6849
};
6950

70-
$.fn.inViewport = function (margin) {
71-
var m = Number.isFinite(margin) ? Number(margin) : 0;
51+
$.fn.inViewport = function (margin = 0) {
52+
var m = Number(margin) || 0;
7253
return this.filter(function () {
7354
if (!(this instanceof Element)) return false;
7455
var rect = this.getBoundingClientRect();
75-
var vpH = window.innerHeight || document.documentElement.clientHeight;
76-
var vpW = window.innerWidth || document.documentElement.clientWidth;
77-
return rect.bottom>=-m && rect.right>=-m && rect.top<=vpH+m && rect.left<=vpW+m;
56+
return rect.bottom >= -m &&
57+
rect.right >= -m &&
58+
rect.top <= window.innerHeight + m &&
59+
rect.left <= window.innerWidth + m;
7860
});
7961
};
8062

81-
try {
82-
Object.defineProperty($.fn, 'exists', {
83-
configurable: true,
84-
get: function () { return this.length > 0; }
85-
});
86-
} catch (_) {}
87-
88-
$.as$ = function (x) { return x && x.jquery ? x : $(x); };
89-
63+
$.as$ = x => x?.jquery ? x : $(x);
9064
$.microUtils = { version: '0.1.2' };
9165

9266
}));
67+

0 commit comments

Comments
 (0)