bitwrench 2.0.8 → 2.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +3 -3
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +2 -2
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +3 -3
- package/dist/bitwrench.cjs.js +202 -22
- package/dist/bitwrench.cjs.min.js +6 -6
- package/dist/bitwrench.es5.js +227 -47
- package/dist/bitwrench.es5.min.js +4 -4
- package/dist/bitwrench.esm.js +202 -22
- package/dist/bitwrench.esm.min.js +6 -6
- package/dist/bitwrench.umd.js +202 -22
- package/dist/bitwrench.umd.min.js +6 -6
- package/dist/builds.json +87 -32
- package/dist/sri.json +17 -12
- package/package.json +11 -3
- package/readme.html +98 -121
- package/src/bitwrench.js +199 -19
- package/src/version.js +3 -3
package/src/bitwrench.js
CHANGED
|
@@ -44,6 +44,27 @@ const bw = {
|
|
|
44
44
|
_unmountCallbacks: new Map(),
|
|
45
45
|
_topics: {}, // topic → [{handler, id}] (plain object for IE11 compat)
|
|
46
46
|
_subIdCounter: 0, // monotonic ID for subscriptions
|
|
47
|
+
|
|
48
|
+
// ── Node reference cache ──────────────────────────────────────────────
|
|
49
|
+
// Fast O(1) lookup for elements by bw_id, id attribute, or bw_uuid.
|
|
50
|
+
//
|
|
51
|
+
// Populated by bw.createDOM() when elements have:
|
|
52
|
+
// - data-bw-id attribute (user-declared addressable elements)
|
|
53
|
+
// - id attribute (standard HTML id)
|
|
54
|
+
// - bw_uuid (internal, for lifecycle-managed elements)
|
|
55
|
+
//
|
|
56
|
+
// Cleaned up by bw.cleanup() when elements are destroyed via bitwrench APIs.
|
|
57
|
+
// On cache miss, falls back to querySelector/getElementById — never fails,
|
|
58
|
+
// just slower. Stale entries (refs to detached nodes) are removed on miss
|
|
59
|
+
// via parentNode === null check (IE11-safe, unlike el.isConnected).
|
|
60
|
+
//
|
|
61
|
+
// Elements created via bw.createDOM() also get el._bw_refs — a local map of
|
|
62
|
+
// child bw_id → DOM node ref for fast parent→child access in o.render.
|
|
63
|
+
// This is the bitwrench equivalent of React's compiled template "holes".
|
|
64
|
+
//
|
|
65
|
+
// Contract: if you remove elements outside of bitwrench APIs (raw el.remove()),
|
|
66
|
+
// map entries may linger until the next lookup attempt cleans them.
|
|
67
|
+
_nodeMap: {},
|
|
47
68
|
|
|
48
69
|
// Monkey patch for testing (same as v1)
|
|
49
70
|
__monkey_patch_is_nodejs__: {
|
|
@@ -274,6 +295,108 @@ bw.uuid = function(prefix) {
|
|
|
274
295
|
return `${tag}${timestamp}_${counter}_${random}`;
|
|
275
296
|
};
|
|
276
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Look up a DOM element by ID string, using the node cache for O(1) access.
|
|
300
|
+
*
|
|
301
|
+
* Resolution order:
|
|
302
|
+
* 1. Check `bw._nodeMap[id]` — if found and still attached (parentNode !== null), return it
|
|
303
|
+
* 2. If cached ref is detached (parentNode === null), remove stale entry
|
|
304
|
+
* 3. Fall back to `document.getElementById(id)` then `document.querySelector(...)`
|
|
305
|
+
* 4. If fallback finds the element, cache it for next time
|
|
306
|
+
* 5. If not found anywhere, return null
|
|
307
|
+
*
|
|
308
|
+
* Accepts a DOM element directly (pass-through) or a string identifier.
|
|
309
|
+
* String identifiers are tried as: direct map key, getElementById,
|
|
310
|
+
* querySelector (for CSS selectors starting with . or #), and
|
|
311
|
+
* data-bw-id attribute selector.
|
|
312
|
+
*
|
|
313
|
+
* @param {string|Element} id - Element ID, CSS selector, data-bw-id value, or DOM element
|
|
314
|
+
* @returns {Element|null} The DOM element, or null if not found
|
|
315
|
+
* @category Internal
|
|
316
|
+
*/
|
|
317
|
+
bw._el = function(id) {
|
|
318
|
+
// Pass-through for DOM elements
|
|
319
|
+
if (typeof id !== 'string') return id || null;
|
|
320
|
+
if (!id) return null;
|
|
321
|
+
if (!bw._isBrowser) return null;
|
|
322
|
+
|
|
323
|
+
// 1. Check cache
|
|
324
|
+
var cached = bw._nodeMap[id];
|
|
325
|
+
if (cached) {
|
|
326
|
+
// Verify not detached (parentNode check is IE11-safe)
|
|
327
|
+
if (cached.parentNode !== null) {
|
|
328
|
+
return cached;
|
|
329
|
+
}
|
|
330
|
+
// Stale — remove and fall through
|
|
331
|
+
delete bw._nodeMap[id];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 2. DOM fallback: try getElementById first (fastest native lookup)
|
|
335
|
+
var el = document.getElementById(id);
|
|
336
|
+
|
|
337
|
+
// 3. Try querySelector for CSS selectors (starts with # or .)
|
|
338
|
+
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
339
|
+
el = document.querySelector(id);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 4. Try data-bw-id attribute (for bw.uuid-generated IDs)
|
|
343
|
+
if (!el) {
|
|
344
|
+
el = document.querySelector('[data-bw-id="' + id + '"]');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 5. Cache the result for next time
|
|
348
|
+
if (el) {
|
|
349
|
+
bw._nodeMap[id] = el;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return el;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Register a DOM element in the node cache under one or more keys.
|
|
357
|
+
*
|
|
358
|
+
* Called internally by `bw.createDOM()`. Registers elements that have
|
|
359
|
+
* id attributes, data-bw-id attributes, or both.
|
|
360
|
+
*
|
|
361
|
+
* @param {Element} el - DOM element to register
|
|
362
|
+
* @param {string} [bwId] - data-bw-id value to register under
|
|
363
|
+
* @category Internal
|
|
364
|
+
*/
|
|
365
|
+
bw._registerNode = function(el, bwId) {
|
|
366
|
+
if (!el) return;
|
|
367
|
+
// Register under data-bw-id
|
|
368
|
+
if (bwId) {
|
|
369
|
+
bw._nodeMap[bwId] = el;
|
|
370
|
+
}
|
|
371
|
+
// Register under id attribute
|
|
372
|
+
var htmlId = el.getAttribute ? el.getAttribute('id') : null;
|
|
373
|
+
if (htmlId) {
|
|
374
|
+
bw._nodeMap[htmlId] = el;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Remove a DOM element from the node cache.
|
|
380
|
+
*
|
|
381
|
+
* Called internally by `bw.cleanup()` when elements are destroyed
|
|
382
|
+
* through bitwrench APIs.
|
|
383
|
+
*
|
|
384
|
+
* @param {Element} el - DOM element to deregister
|
|
385
|
+
* @param {string} [bwId] - data-bw-id value to remove
|
|
386
|
+
* @category Internal
|
|
387
|
+
*/
|
|
388
|
+
bw._deregisterNode = function(el, bwId) {
|
|
389
|
+
// Remove data-bw-id entry
|
|
390
|
+
if (bwId) {
|
|
391
|
+
delete bw._nodeMap[bwId];
|
|
392
|
+
}
|
|
393
|
+
// Remove id attribute entry
|
|
394
|
+
var htmlId = el && el.getAttribute ? el.getAttribute('id') : null;
|
|
395
|
+
if (htmlId) {
|
|
396
|
+
delete bw._nodeMap[htmlId];
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
277
400
|
/**
|
|
278
401
|
* Escape HTML special characters to prevent XSS.
|
|
279
402
|
*
|
|
@@ -498,26 +621,66 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
498
621
|
}
|
|
499
622
|
}
|
|
500
623
|
|
|
501
|
-
// Add children
|
|
624
|
+
// Add children, building _bw_refs for fast parent→child access.
|
|
625
|
+
// Children with data-bw-id or id attributes get local refs on the parent,
|
|
626
|
+
// so o.render functions can access them without any DOM lookup.
|
|
502
627
|
if (content != null) {
|
|
503
628
|
if (Array.isArray(content)) {
|
|
504
629
|
content.forEach(child => {
|
|
505
630
|
if (child != null) {
|
|
506
|
-
|
|
631
|
+
var childEl = bw.createDOM(child, options);
|
|
632
|
+
el.appendChild(childEl);
|
|
633
|
+
// Build local refs for addressable children
|
|
634
|
+
var childBwId = (child && child.a) ? (child.a['data-bw-id'] || child.a.id) : null;
|
|
635
|
+
if (childBwId) {
|
|
636
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
637
|
+
el._bw_refs[childBwId] = childEl;
|
|
638
|
+
}
|
|
639
|
+
// Bubble up grandchild refs (flatten one level)
|
|
640
|
+
if (childEl._bw_refs) {
|
|
641
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
642
|
+
for (var rk in childEl._bw_refs) {
|
|
643
|
+
if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
|
|
644
|
+
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
507
648
|
}
|
|
508
649
|
});
|
|
509
650
|
} else if (typeof content === 'object' && content.t) {
|
|
510
|
-
|
|
651
|
+
var childEl = bw.createDOM(content, options);
|
|
652
|
+
el.appendChild(childEl);
|
|
653
|
+
var childBwId = content.a ? (content.a['data-bw-id'] || content.a.id) : null;
|
|
654
|
+
if (childBwId) {
|
|
655
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
656
|
+
el._bw_refs[childBwId] = childEl;
|
|
657
|
+
}
|
|
658
|
+
if (childEl._bw_refs) {
|
|
659
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
660
|
+
for (var rk in childEl._bw_refs) {
|
|
661
|
+
if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
|
|
662
|
+
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
511
666
|
} else {
|
|
512
667
|
el.textContent = String(content);
|
|
513
668
|
}
|
|
514
669
|
}
|
|
515
|
-
|
|
670
|
+
|
|
671
|
+
// Register element in node cache if it has an id attribute
|
|
672
|
+
if (attrs.id) {
|
|
673
|
+
bw._registerNode(el, null);
|
|
674
|
+
}
|
|
675
|
+
|
|
516
676
|
// Handle lifecycle hooks and state
|
|
517
677
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
518
678
|
const id = attrs['data-bw-id'] || bw.uuid();
|
|
519
679
|
el.setAttribute('data-bw-id', id);
|
|
520
680
|
|
|
681
|
+
// Register in node cache under data-bw-id
|
|
682
|
+
bw._registerNode(el, id);
|
|
683
|
+
|
|
521
684
|
// Store state
|
|
522
685
|
if (opts.state) {
|
|
523
686
|
el._bw_state = opts.state;
|
|
@@ -560,8 +723,11 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
560
723
|
opts.unmount(el, el._bw_state || {});
|
|
561
724
|
});
|
|
562
725
|
}
|
|
726
|
+
} else if (attrs['data-bw-id']) {
|
|
727
|
+
// Element has explicit data-bw-id but no lifecycle hooks — still register it
|
|
728
|
+
bw._registerNode(el, attrs['data-bw-id']);
|
|
563
729
|
}
|
|
564
|
-
|
|
730
|
+
|
|
565
731
|
return el;
|
|
566
732
|
};
|
|
567
733
|
|
|
@@ -594,10 +760,8 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
594
760
|
throw new Error('bw.DOM requires a DOM environment (document/window). Use bw.html() instead.');
|
|
595
761
|
}
|
|
596
762
|
|
|
597
|
-
// Get target element
|
|
598
|
-
const targetEl =
|
|
599
|
-
? document.querySelector(target)
|
|
600
|
-
: target;
|
|
763
|
+
// Get target element (use cache-backed lookup)
|
|
764
|
+
const targetEl = bw._el(target);
|
|
601
765
|
|
|
602
766
|
if (!targetEl) {
|
|
603
767
|
console.error('bw.DOM: Target element not found:', target);
|
|
@@ -620,7 +784,11 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
620
784
|
// Restore the target's own state/render/subs after cleanup
|
|
621
785
|
if (savedState !== undefined) targetEl._bw_state = savedState;
|
|
622
786
|
if (savedRender) targetEl._bw_render = savedRender;
|
|
623
|
-
if (savedBwId)
|
|
787
|
+
if (savedBwId) {
|
|
788
|
+
targetEl.setAttribute('data-bw-id', savedBwId);
|
|
789
|
+
// Re-register mount point in node cache (cleanup deregistered it)
|
|
790
|
+
bw._registerNode(targetEl, savedBwId);
|
|
791
|
+
}
|
|
624
792
|
if (savedSubs) targetEl._bw_subs = savedSubs;
|
|
625
793
|
|
|
626
794
|
// Clear and mount new content
|
|
@@ -883,15 +1051,19 @@ bw.cleanup = function(element) {
|
|
|
883
1051
|
bw._unmountCallbacks.delete(id);
|
|
884
1052
|
}
|
|
885
1053
|
|
|
1054
|
+
// Deregister from node cache
|
|
1055
|
+
bw._deregisterNode(el, id);
|
|
1056
|
+
|
|
886
1057
|
// Clean up pub/sub subscriptions tied to this element
|
|
887
1058
|
if (el._bw_subs) {
|
|
888
1059
|
el._bw_subs.forEach(function(unsub) { unsub(); });
|
|
889
1060
|
delete el._bw_subs;
|
|
890
1061
|
}
|
|
891
1062
|
|
|
892
|
-
// Clean up state and
|
|
1063
|
+
// Clean up state, render, and local refs
|
|
893
1064
|
delete el._bw_state;
|
|
894
1065
|
delete el._bw_render;
|
|
1066
|
+
delete el._bw_refs;
|
|
895
1067
|
});
|
|
896
1068
|
|
|
897
1069
|
// Check element itself
|
|
@@ -902,6 +1074,10 @@ bw.cleanup = function(element) {
|
|
|
902
1074
|
callback();
|
|
903
1075
|
bw._unmountCallbacks.delete(id);
|
|
904
1076
|
}
|
|
1077
|
+
|
|
1078
|
+
// Deregister from node cache
|
|
1079
|
+
bw._deregisterNode(element, id);
|
|
1080
|
+
|
|
905
1081
|
// Clean up pub/sub subscriptions tied to element itself
|
|
906
1082
|
if (element._bw_subs) {
|
|
907
1083
|
element._bw_subs.forEach(function(unsub) { unsub(); });
|
|
@@ -909,6 +1085,7 @@ bw.cleanup = function(element) {
|
|
|
909
1085
|
}
|
|
910
1086
|
delete element._bw_state;
|
|
911
1087
|
delete element._bw_render;
|
|
1088
|
+
delete element._bw_refs;
|
|
912
1089
|
}
|
|
913
1090
|
};
|
|
914
1091
|
|
|
@@ -923,7 +1100,7 @@ bw.cleanup = function(element) {
|
|
|
923
1100
|
* Calls `el._bw_render(el, state)` and emits `bw:statechange` so other
|
|
924
1101
|
* components can react without tight coupling.
|
|
925
1102
|
*
|
|
926
|
-
* @param {string|Element} target - CSS selector or DOM element
|
|
1103
|
+
* @param {string|Element} target - Element ID, data-bw-id, CSS selector, or DOM element
|
|
927
1104
|
* @returns {Element|null} The element, or null if not found / no render function
|
|
928
1105
|
* @category State Management
|
|
929
1106
|
* @see bw.patch
|
|
@@ -933,7 +1110,7 @@ bw.cleanup = function(element) {
|
|
|
933
1110
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
934
1111
|
*/
|
|
935
1112
|
bw.update = function(target) {
|
|
936
|
-
var el =
|
|
1113
|
+
var el = bw._el(target);
|
|
937
1114
|
if (el && el._bw_render) {
|
|
938
1115
|
el._bw_render(el, el._bw_state || {});
|
|
939
1116
|
bw.emit(el, 'statechange', el._bw_state);
|
|
@@ -948,7 +1125,8 @@ bw.update = function(target) {
|
|
|
948
1125
|
* Use `bw.patch()` for lightweight value updates (scores, labels, counters)
|
|
949
1126
|
* and `bw.update()` for full structural re-renders.
|
|
950
1127
|
*
|
|
951
|
-
* @param {string|Element} id - Element ID
|
|
1128
|
+
* @param {string|Element} id - Element ID, data-bw-id, CSS selector, or DOM element.
|
|
1129
|
+
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
952
1130
|
* @param {string|Object} content - New text content, or TACO object to replace children
|
|
953
1131
|
* @param {string} [attr] - If provided, sets this attribute instead of content
|
|
954
1132
|
* @returns {Element|null} The patched element, or null if not found
|
|
@@ -961,7 +1139,7 @@ bw.update = function(target) {
|
|
|
961
1139
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
962
1140
|
*/
|
|
963
1141
|
bw.patch = function(id, content, attr) {
|
|
964
|
-
var el =
|
|
1142
|
+
var el = bw._el(id);
|
|
965
1143
|
if (!el) return null;
|
|
966
1144
|
|
|
967
1145
|
if (attr) {
|
|
@@ -1012,7 +1190,8 @@ bw.patchAll = function(patches) {
|
|
|
1012
1190
|
* bubble by default so ancestor elements can listen. Use with `bw.on()` for
|
|
1013
1191
|
* DOM-scoped communication between components.
|
|
1014
1192
|
*
|
|
1015
|
-
* @param {string|Element} target - CSS selector or DOM element
|
|
1193
|
+
* @param {string|Element} target - Element ID, data-bw-id, CSS selector, or DOM element.
|
|
1194
|
+
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
1016
1195
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
1017
1196
|
* @param {*} [detail] - Data to pass with the event
|
|
1018
1197
|
* @category Events (DOM)
|
|
@@ -1022,7 +1201,7 @@ bw.patchAll = function(patches) {
|
|
|
1022
1201
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
1023
1202
|
*/
|
|
1024
1203
|
bw.emit = function(target, eventName, detail) {
|
|
1025
|
-
var el =
|
|
1204
|
+
var el = bw._el(target);
|
|
1026
1205
|
if (el) {
|
|
1027
1206
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
1028
1207
|
bubbles: true,
|
|
@@ -1038,7 +1217,8 @@ bw.emit = function(target, eventName, detail) {
|
|
|
1038
1217
|
* is the first argument so you don't need to destructure `e.detail`.
|
|
1039
1218
|
* Events bubble, so you can listen on an ancestor element.
|
|
1040
1219
|
*
|
|
1041
|
-
* @param {string|Element} target - CSS selector or DOM element
|
|
1220
|
+
* @param {string|Element} target - Element ID, data-bw-id, CSS selector, or DOM element.
|
|
1221
|
+
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
1042
1222
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
1043
1223
|
* @param {Function} handler - Called with (detail, event)
|
|
1044
1224
|
* @returns {Element|null} The element (for chaining), or null if not found
|
|
@@ -1050,7 +1230,7 @@ bw.emit = function(target, eventName, detail) {
|
|
|
1050
1230
|
* });
|
|
1051
1231
|
*/
|
|
1052
1232
|
bw.on = function(target, eventName, handler) {
|
|
1053
|
-
var el =
|
|
1233
|
+
var el = bw._el(target);
|
|
1054
1234
|
if (el) {
|
|
1055
1235
|
el.addEventListener('bw:' + eventName, function(e) {
|
|
1056
1236
|
handler(e.detail, e);
|
package/src/version.js
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* DO NOT EDIT DIRECTLY - Use npm run generate-version
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export const VERSION = '2.0.
|
|
6
|
+
export const VERSION = '2.0.10';
|
|
7
7
|
export const VERSION_INFO = {
|
|
8
|
-
version: '2.0.
|
|
8
|
+
version: '2.0.10',
|
|
9
9
|
name: 'bitwrench',
|
|
10
10
|
description: 'A library for javascript UI functions.',
|
|
11
11
|
license: 'BSD-2-Clause',
|
|
12
12
|
homepage: 'http://deftio.com/bitwrench',
|
|
13
13
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
14
14
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
15
|
-
buildDate: '2026-03-
|
|
15
|
+
buildDate: '2026-03-07T03:14:16.606Z'
|
|
16
16
|
};
|