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/dist/bitwrench.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.10 | BSD-2-Clause | http://deftio.com/bitwrench */
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const VERSION_INFO = {
|
|
14
|
-
version: '2.0.
|
|
14
|
+
version: '2.0.10',
|
|
15
15
|
name: 'bitwrench',
|
|
16
16
|
description: 'A library for javascript UI functions.',
|
|
17
17
|
license: 'BSD-2-Clause',
|
|
18
18
|
homepage: 'http://deftio.com/bitwrench',
|
|
19
19
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
20
20
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
21
|
-
buildDate: '2026-03-
|
|
21
|
+
buildDate: '2026-03-07T03:14:16.606Z'
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -3669,6 +3669,27 @@
|
|
|
3669
3669
|
_unmountCallbacks: new Map(),
|
|
3670
3670
|
_topics: {}, // topic → [{handler, id}] (plain object for IE11 compat)
|
|
3671
3671
|
_subIdCounter: 0, // monotonic ID for subscriptions
|
|
3672
|
+
|
|
3673
|
+
// ── Node reference cache ──────────────────────────────────────────────
|
|
3674
|
+
// Fast O(1) lookup for elements by bw_id, id attribute, or bw_uuid.
|
|
3675
|
+
//
|
|
3676
|
+
// Populated by bw.createDOM() when elements have:
|
|
3677
|
+
// - data-bw-id attribute (user-declared addressable elements)
|
|
3678
|
+
// - id attribute (standard HTML id)
|
|
3679
|
+
// - bw_uuid (internal, for lifecycle-managed elements)
|
|
3680
|
+
//
|
|
3681
|
+
// Cleaned up by bw.cleanup() when elements are destroyed via bitwrench APIs.
|
|
3682
|
+
// On cache miss, falls back to querySelector/getElementById — never fails,
|
|
3683
|
+
// just slower. Stale entries (refs to detached nodes) are removed on miss
|
|
3684
|
+
// via parentNode === null check (IE11-safe, unlike el.isConnected).
|
|
3685
|
+
//
|
|
3686
|
+
// Elements created via bw.createDOM() also get el._bw_refs — a local map of
|
|
3687
|
+
// child bw_id → DOM node ref for fast parent→child access in o.render.
|
|
3688
|
+
// This is the bitwrench equivalent of React's compiled template "holes".
|
|
3689
|
+
//
|
|
3690
|
+
// Contract: if you remove elements outside of bitwrench APIs (raw el.remove()),
|
|
3691
|
+
// map entries may linger until the next lookup attempt cleans them.
|
|
3692
|
+
_nodeMap: {},
|
|
3672
3693
|
|
|
3673
3694
|
// Monkey patch for testing (same as v1)
|
|
3674
3695
|
__monkey_patch_is_nodejs__: {
|
|
@@ -3899,6 +3920,108 @@
|
|
|
3899
3920
|
return `${tag}${timestamp}_${counter}_${random}`;
|
|
3900
3921
|
};
|
|
3901
3922
|
|
|
3923
|
+
/**
|
|
3924
|
+
* Look up a DOM element by ID string, using the node cache for O(1) access.
|
|
3925
|
+
*
|
|
3926
|
+
* Resolution order:
|
|
3927
|
+
* 1. Check `bw._nodeMap[id]` — if found and still attached (parentNode !== null), return it
|
|
3928
|
+
* 2. If cached ref is detached (parentNode === null), remove stale entry
|
|
3929
|
+
* 3. Fall back to `document.getElementById(id)` then `document.querySelector(...)`
|
|
3930
|
+
* 4. If fallback finds the element, cache it for next time
|
|
3931
|
+
* 5. If not found anywhere, return null
|
|
3932
|
+
*
|
|
3933
|
+
* Accepts a DOM element directly (pass-through) or a string identifier.
|
|
3934
|
+
* String identifiers are tried as: direct map key, getElementById,
|
|
3935
|
+
* querySelector (for CSS selectors starting with . or #), and
|
|
3936
|
+
* data-bw-id attribute selector.
|
|
3937
|
+
*
|
|
3938
|
+
* @param {string|Element} id - Element ID, CSS selector, data-bw-id value, or DOM element
|
|
3939
|
+
* @returns {Element|null} The DOM element, or null if not found
|
|
3940
|
+
* @category Internal
|
|
3941
|
+
*/
|
|
3942
|
+
bw._el = function(id) {
|
|
3943
|
+
// Pass-through for DOM elements
|
|
3944
|
+
if (typeof id !== 'string') return id || null;
|
|
3945
|
+
if (!id) return null;
|
|
3946
|
+
if (!bw._isBrowser) return null;
|
|
3947
|
+
|
|
3948
|
+
// 1. Check cache
|
|
3949
|
+
var cached = bw._nodeMap[id];
|
|
3950
|
+
if (cached) {
|
|
3951
|
+
// Verify not detached (parentNode check is IE11-safe)
|
|
3952
|
+
if (cached.parentNode !== null) {
|
|
3953
|
+
return cached;
|
|
3954
|
+
}
|
|
3955
|
+
// Stale — remove and fall through
|
|
3956
|
+
delete bw._nodeMap[id];
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
// 2. DOM fallback: try getElementById first (fastest native lookup)
|
|
3960
|
+
var el = document.getElementById(id);
|
|
3961
|
+
|
|
3962
|
+
// 3. Try querySelector for CSS selectors (starts with # or .)
|
|
3963
|
+
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
3964
|
+
el = document.querySelector(id);
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
// 4. Try data-bw-id attribute (for bw.uuid-generated IDs)
|
|
3968
|
+
if (!el) {
|
|
3969
|
+
el = document.querySelector('[data-bw-id="' + id + '"]');
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
// 5. Cache the result for next time
|
|
3973
|
+
if (el) {
|
|
3974
|
+
bw._nodeMap[id] = el;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
return el;
|
|
3978
|
+
};
|
|
3979
|
+
|
|
3980
|
+
/**
|
|
3981
|
+
* Register a DOM element in the node cache under one or more keys.
|
|
3982
|
+
*
|
|
3983
|
+
* Called internally by `bw.createDOM()`. Registers elements that have
|
|
3984
|
+
* id attributes, data-bw-id attributes, or both.
|
|
3985
|
+
*
|
|
3986
|
+
* @param {Element} el - DOM element to register
|
|
3987
|
+
* @param {string} [bwId] - data-bw-id value to register under
|
|
3988
|
+
* @category Internal
|
|
3989
|
+
*/
|
|
3990
|
+
bw._registerNode = function(el, bwId) {
|
|
3991
|
+
if (!el) return;
|
|
3992
|
+
// Register under data-bw-id
|
|
3993
|
+
if (bwId) {
|
|
3994
|
+
bw._nodeMap[bwId] = el;
|
|
3995
|
+
}
|
|
3996
|
+
// Register under id attribute
|
|
3997
|
+
var htmlId = el.getAttribute ? el.getAttribute('id') : null;
|
|
3998
|
+
if (htmlId) {
|
|
3999
|
+
bw._nodeMap[htmlId] = el;
|
|
4000
|
+
}
|
|
4001
|
+
};
|
|
4002
|
+
|
|
4003
|
+
/**
|
|
4004
|
+
* Remove a DOM element from the node cache.
|
|
4005
|
+
*
|
|
4006
|
+
* Called internally by `bw.cleanup()` when elements are destroyed
|
|
4007
|
+
* through bitwrench APIs.
|
|
4008
|
+
*
|
|
4009
|
+
* @param {Element} el - DOM element to deregister
|
|
4010
|
+
* @param {string} [bwId] - data-bw-id value to remove
|
|
4011
|
+
* @category Internal
|
|
4012
|
+
*/
|
|
4013
|
+
bw._deregisterNode = function(el, bwId) {
|
|
4014
|
+
// Remove data-bw-id entry
|
|
4015
|
+
if (bwId) {
|
|
4016
|
+
delete bw._nodeMap[bwId];
|
|
4017
|
+
}
|
|
4018
|
+
// Remove id attribute entry
|
|
4019
|
+
var htmlId = el && el.getAttribute ? el.getAttribute('id') : null;
|
|
4020
|
+
if (htmlId) {
|
|
4021
|
+
delete bw._nodeMap[htmlId];
|
|
4022
|
+
}
|
|
4023
|
+
};
|
|
4024
|
+
|
|
3902
4025
|
/**
|
|
3903
4026
|
* Escape HTML special characters to prevent XSS.
|
|
3904
4027
|
*
|
|
@@ -4123,26 +4246,66 @@
|
|
|
4123
4246
|
}
|
|
4124
4247
|
}
|
|
4125
4248
|
|
|
4126
|
-
// Add children
|
|
4249
|
+
// Add children, building _bw_refs for fast parent→child access.
|
|
4250
|
+
// Children with data-bw-id or id attributes get local refs on the parent,
|
|
4251
|
+
// so o.render functions can access them without any DOM lookup.
|
|
4127
4252
|
if (content != null) {
|
|
4128
4253
|
if (Array.isArray(content)) {
|
|
4129
4254
|
content.forEach(child => {
|
|
4130
4255
|
if (child != null) {
|
|
4131
|
-
|
|
4256
|
+
var childEl = bw.createDOM(child, options);
|
|
4257
|
+
el.appendChild(childEl);
|
|
4258
|
+
// Build local refs for addressable children
|
|
4259
|
+
var childBwId = (child && child.a) ? (child.a['data-bw-id'] || child.a.id) : null;
|
|
4260
|
+
if (childBwId) {
|
|
4261
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
4262
|
+
el._bw_refs[childBwId] = childEl;
|
|
4263
|
+
}
|
|
4264
|
+
// Bubble up grandchild refs (flatten one level)
|
|
4265
|
+
if (childEl._bw_refs) {
|
|
4266
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
4267
|
+
for (var rk in childEl._bw_refs) {
|
|
4268
|
+
if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
|
|
4269
|
+
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4132
4273
|
}
|
|
4133
4274
|
});
|
|
4134
4275
|
} else if (typeof content === 'object' && content.t) {
|
|
4135
|
-
|
|
4276
|
+
var childEl = bw.createDOM(content, options);
|
|
4277
|
+
el.appendChild(childEl);
|
|
4278
|
+
var childBwId = content.a ? (content.a['data-bw-id'] || content.a.id) : null;
|
|
4279
|
+
if (childBwId) {
|
|
4280
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
4281
|
+
el._bw_refs[childBwId] = childEl;
|
|
4282
|
+
}
|
|
4283
|
+
if (childEl._bw_refs) {
|
|
4284
|
+
if (!el._bw_refs) el._bw_refs = {};
|
|
4285
|
+
for (var rk in childEl._bw_refs) {
|
|
4286
|
+
if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
|
|
4287
|
+
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4136
4291
|
} else {
|
|
4137
4292
|
el.textContent = String(content);
|
|
4138
4293
|
}
|
|
4139
4294
|
}
|
|
4140
|
-
|
|
4295
|
+
|
|
4296
|
+
// Register element in node cache if it has an id attribute
|
|
4297
|
+
if (attrs.id) {
|
|
4298
|
+
bw._registerNode(el, null);
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4141
4301
|
// Handle lifecycle hooks and state
|
|
4142
4302
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
4143
4303
|
const id = attrs['data-bw-id'] || bw.uuid();
|
|
4144
4304
|
el.setAttribute('data-bw-id', id);
|
|
4145
4305
|
|
|
4306
|
+
// Register in node cache under data-bw-id
|
|
4307
|
+
bw._registerNode(el, id);
|
|
4308
|
+
|
|
4146
4309
|
// Store state
|
|
4147
4310
|
if (opts.state) {
|
|
4148
4311
|
el._bw_state = opts.state;
|
|
@@ -4185,8 +4348,11 @@
|
|
|
4185
4348
|
opts.unmount(el, el._bw_state || {});
|
|
4186
4349
|
});
|
|
4187
4350
|
}
|
|
4351
|
+
} else if (attrs['data-bw-id']) {
|
|
4352
|
+
// Element has explicit data-bw-id but no lifecycle hooks — still register it
|
|
4353
|
+
bw._registerNode(el, attrs['data-bw-id']);
|
|
4188
4354
|
}
|
|
4189
|
-
|
|
4355
|
+
|
|
4190
4356
|
return el;
|
|
4191
4357
|
};
|
|
4192
4358
|
|
|
@@ -4219,10 +4385,8 @@
|
|
|
4219
4385
|
throw new Error('bw.DOM requires a DOM environment (document/window). Use bw.html() instead.');
|
|
4220
4386
|
}
|
|
4221
4387
|
|
|
4222
|
-
// Get target element
|
|
4223
|
-
const targetEl =
|
|
4224
|
-
? document.querySelector(target)
|
|
4225
|
-
: target;
|
|
4388
|
+
// Get target element (use cache-backed lookup)
|
|
4389
|
+
const targetEl = bw._el(target);
|
|
4226
4390
|
|
|
4227
4391
|
if (!targetEl) {
|
|
4228
4392
|
console.error('bw.DOM: Target element not found:', target);
|
|
@@ -4245,7 +4409,11 @@
|
|
|
4245
4409
|
// Restore the target's own state/render/subs after cleanup
|
|
4246
4410
|
if (savedState !== undefined) targetEl._bw_state = savedState;
|
|
4247
4411
|
if (savedRender) targetEl._bw_render = savedRender;
|
|
4248
|
-
if (savedBwId)
|
|
4412
|
+
if (savedBwId) {
|
|
4413
|
+
targetEl.setAttribute('data-bw-id', savedBwId);
|
|
4414
|
+
// Re-register mount point in node cache (cleanup deregistered it)
|
|
4415
|
+
bw._registerNode(targetEl, savedBwId);
|
|
4416
|
+
}
|
|
4249
4417
|
if (savedSubs) targetEl._bw_subs = savedSubs;
|
|
4250
4418
|
|
|
4251
4419
|
// Clear and mount new content
|
|
@@ -4508,15 +4676,19 @@
|
|
|
4508
4676
|
bw._unmountCallbacks.delete(id);
|
|
4509
4677
|
}
|
|
4510
4678
|
|
|
4679
|
+
// Deregister from node cache
|
|
4680
|
+
bw._deregisterNode(el, id);
|
|
4681
|
+
|
|
4511
4682
|
// Clean up pub/sub subscriptions tied to this element
|
|
4512
4683
|
if (el._bw_subs) {
|
|
4513
4684
|
el._bw_subs.forEach(function(unsub) { unsub(); });
|
|
4514
4685
|
delete el._bw_subs;
|
|
4515
4686
|
}
|
|
4516
4687
|
|
|
4517
|
-
// Clean up state and
|
|
4688
|
+
// Clean up state, render, and local refs
|
|
4518
4689
|
delete el._bw_state;
|
|
4519
4690
|
delete el._bw_render;
|
|
4691
|
+
delete el._bw_refs;
|
|
4520
4692
|
});
|
|
4521
4693
|
|
|
4522
4694
|
// Check element itself
|
|
@@ -4527,6 +4699,10 @@
|
|
|
4527
4699
|
callback();
|
|
4528
4700
|
bw._unmountCallbacks.delete(id);
|
|
4529
4701
|
}
|
|
4702
|
+
|
|
4703
|
+
// Deregister from node cache
|
|
4704
|
+
bw._deregisterNode(element, id);
|
|
4705
|
+
|
|
4530
4706
|
// Clean up pub/sub subscriptions tied to element itself
|
|
4531
4707
|
if (element._bw_subs) {
|
|
4532
4708
|
element._bw_subs.forEach(function(unsub) { unsub(); });
|
|
@@ -4534,6 +4710,7 @@
|
|
|
4534
4710
|
}
|
|
4535
4711
|
delete element._bw_state;
|
|
4536
4712
|
delete element._bw_render;
|
|
4713
|
+
delete element._bw_refs;
|
|
4537
4714
|
}
|
|
4538
4715
|
};
|
|
4539
4716
|
|
|
@@ -4548,7 +4725,7 @@
|
|
|
4548
4725
|
* Calls `el._bw_render(el, state)` and emits `bw:statechange` so other
|
|
4549
4726
|
* components can react without tight coupling.
|
|
4550
4727
|
*
|
|
4551
|
-
* @param {string|Element} target - CSS selector or DOM element
|
|
4728
|
+
* @param {string|Element} target - Element ID, data-bw-id, CSS selector, or DOM element
|
|
4552
4729
|
* @returns {Element|null} The element, or null if not found / no render function
|
|
4553
4730
|
* @category State Management
|
|
4554
4731
|
* @see bw.patch
|
|
@@ -4558,7 +4735,7 @@
|
|
|
4558
4735
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
4559
4736
|
*/
|
|
4560
4737
|
bw.update = function(target) {
|
|
4561
|
-
var el =
|
|
4738
|
+
var el = bw._el(target);
|
|
4562
4739
|
if (el && el._bw_render) {
|
|
4563
4740
|
el._bw_render(el, el._bw_state || {});
|
|
4564
4741
|
bw.emit(el, 'statechange', el._bw_state);
|
|
@@ -4573,7 +4750,8 @@
|
|
|
4573
4750
|
* Use `bw.patch()` for lightweight value updates (scores, labels, counters)
|
|
4574
4751
|
* and `bw.update()` for full structural re-renders.
|
|
4575
4752
|
*
|
|
4576
|
-
* @param {string|Element} id - Element ID
|
|
4753
|
+
* @param {string|Element} id - Element ID, data-bw-id, CSS selector, or DOM element.
|
|
4754
|
+
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
4577
4755
|
* @param {string|Object} content - New text content, or TACO object to replace children
|
|
4578
4756
|
* @param {string} [attr] - If provided, sets this attribute instead of content
|
|
4579
4757
|
* @returns {Element|null} The patched element, or null if not found
|
|
@@ -4586,7 +4764,7 @@
|
|
|
4586
4764
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
4587
4765
|
*/
|
|
4588
4766
|
bw.patch = function(id, content, attr) {
|
|
4589
|
-
var el =
|
|
4767
|
+
var el = bw._el(id);
|
|
4590
4768
|
if (!el) return null;
|
|
4591
4769
|
|
|
4592
4770
|
if (attr) {
|
|
@@ -4637,7 +4815,8 @@
|
|
|
4637
4815
|
* bubble by default so ancestor elements can listen. Use with `bw.on()` for
|
|
4638
4816
|
* DOM-scoped communication between components.
|
|
4639
4817
|
*
|
|
4640
|
-
* @param {string|Element} target - CSS selector or DOM element
|
|
4818
|
+
* @param {string|Element} target - Element ID, data-bw-id, CSS selector, or DOM element.
|
|
4819
|
+
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
4641
4820
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
4642
4821
|
* @param {*} [detail] - Data to pass with the event
|
|
4643
4822
|
* @category Events (DOM)
|
|
@@ -4647,7 +4826,7 @@
|
|
|
4647
4826
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
4648
4827
|
*/
|
|
4649
4828
|
bw.emit = function(target, eventName, detail) {
|
|
4650
|
-
var el =
|
|
4829
|
+
var el = bw._el(target);
|
|
4651
4830
|
if (el) {
|
|
4652
4831
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
4653
4832
|
bubbles: true,
|
|
@@ -4663,7 +4842,8 @@
|
|
|
4663
4842
|
* is the first argument so you don't need to destructure `e.detail`.
|
|
4664
4843
|
* Events bubble, so you can listen on an ancestor element.
|
|
4665
4844
|
*
|
|
4666
|
-
* @param {string|Element} target - CSS selector or DOM element
|
|
4845
|
+
* @param {string|Element} target - Element ID, data-bw-id, CSS selector, or DOM element.
|
|
4846
|
+
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
4667
4847
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
4668
4848
|
* @param {Function} handler - Called with (detail, event)
|
|
4669
4849
|
* @returns {Element|null} The element (for chaining), or null if not found
|
|
@@ -4675,7 +4855,7 @@
|
|
|
4675
4855
|
* });
|
|
4676
4856
|
*/
|
|
4677
4857
|
bw.on = function(target, eventName, handler) {
|
|
4678
|
-
var el =
|
|
4858
|
+
var el = bw._el(target);
|
|
4679
4859
|
if (el) {
|
|
4680
4860
|
el.addEventListener('bw:' + eventName, function(e) {
|
|
4681
4861
|
handler(e.detail, e);
|