@jqhtml/core 2.3.71 → 2.3.72
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/component.d.ts +49 -0
- package/dist/component.d.ts.map +1 -1
- package/dist/dom-attachment.d.ts +31 -0
- package/dist/dom-attachment.d.ts.map +1 -0
- package/dist/index.cjs +253 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -62
- package/dist/index.js.map +1 -1
- package/dist/jqhtml-core.esm.js +253 -63
- package/dist/jqhtml-core.esm.js.map +1 -1
- package/dist/jquery-plugin.d.ts.map +1 -1
- package/dist/lifecycle-manager.d.ts +0 -5
- package/dist/lifecycle-manager.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/component.d.ts
CHANGED
|
@@ -77,6 +77,9 @@ export declare class Jqhtml_Component {
|
|
|
77
77
|
private __load_gates;
|
|
78
78
|
private __first_load_started;
|
|
79
79
|
private __gate_resume;
|
|
80
|
+
private _attached;
|
|
81
|
+
private _attachment_checked;
|
|
82
|
+
private _attachment_tracking;
|
|
80
83
|
constructor(element?: any, args?: Record<string, any>);
|
|
81
84
|
/**
|
|
82
85
|
* Protect lifecycle methods from manual invocation
|
|
@@ -405,6 +408,46 @@ export declare class Jqhtml_Component {
|
|
|
405
408
|
on_loaded(): void | Promise<void>;
|
|
406
409
|
on_ready(): Promise<void>;
|
|
407
410
|
on_stop(): void;
|
|
411
|
+
on_attach(): void;
|
|
412
|
+
on_detach(): void;
|
|
413
|
+
/**
|
|
414
|
+
* Whether this component currently considers itself attached to the document.
|
|
415
|
+
*
|
|
416
|
+
* This is the component's recorded state, not a live isConnected read: it is
|
|
417
|
+
* false until the first check after 'ready', and from then on flips only when a
|
|
418
|
+
* transition is observed (or on stop()). It is exactly the state 'attach' and
|
|
419
|
+
* 'detach' have reported.
|
|
420
|
+
*/
|
|
421
|
+
is_attached(): boolean;
|
|
422
|
+
/**
|
|
423
|
+
* Flip the recorded state and fire the matching hook + event. The opposite
|
|
424
|
+
* sticky event is invalidated first, so a late subscriber only ever sees the
|
|
425
|
+
* current state. Hook errors are isolated; the event still fires.
|
|
426
|
+
* @private
|
|
427
|
+
*/
|
|
428
|
+
private _transition_attachment;
|
|
429
|
+
/**
|
|
430
|
+
* Compare the root's live connectivity with the recorded state and transition
|
|
431
|
+
* on a difference. Idempotent. Called by the document observer on every
|
|
432
|
+
* mutation batch for tracked components, and directly by the framework after
|
|
433
|
+
* 'ready' and when interest is registered late.
|
|
434
|
+
* @private
|
|
435
|
+
*/
|
|
436
|
+
_attachment_check(): void;
|
|
437
|
+
/**
|
|
438
|
+
* The first check, run once the 'ready' handlers have completed. Fires 'attach'
|
|
439
|
+
* if the component was born attached, even with no listener, so a later
|
|
440
|
+
* .on('attach') gets the sticky fire. Deferred interest starts tracking here.
|
|
441
|
+
* @private
|
|
442
|
+
*/
|
|
443
|
+
private _attachment_first_check;
|
|
444
|
+
/**
|
|
445
|
+
* Register interest in attach/detach. Before the first ready check this only
|
|
446
|
+
* records the interest; after it, tracking starts immediately and a check runs
|
|
447
|
+
* at once, so a late subscriber on a natively-removed component sees 'detach'.
|
|
448
|
+
* @private
|
|
449
|
+
*/
|
|
450
|
+
private _watch_attachment;
|
|
408
451
|
/**
|
|
409
452
|
* Called with the viewport width in CSS pixels:
|
|
410
453
|
* - after every on_render()
|
|
@@ -461,6 +504,12 @@ export declare class Jqhtml_Component {
|
|
|
461
504
|
/**
|
|
462
505
|
* Trigger a lifecycle event - delegates to component-events.ts
|
|
463
506
|
* @see component-events.ts for full documentation
|
|
507
|
+
*
|
|
508
|
+
* 'ready' is special: once its handlers have run, the component runs its first
|
|
509
|
+
* attachment check (and every later one, idempotently). Doing it here rather than
|
|
510
|
+
* at each of the call sites that trigger 'ready' guarantees the order
|
|
511
|
+
* on_ready() -> 'ready' handlers -> 'attach', so everything on_ready() set up is
|
|
512
|
+
* complete before anyone learns the component is in the document.
|
|
464
513
|
*/
|
|
465
514
|
trigger(event_name: string, data?: any): void;
|
|
466
515
|
/**
|
package/dist/component.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JQHTML DOM Attachment Tracking
|
|
3
|
+
*
|
|
4
|
+
* Backs the component 'attach' / 'detach' lifecycle events.
|
|
5
|
+
*
|
|
6
|
+
* One document-level MutationObserver (childList + subtree) is installed the first
|
|
7
|
+
* time any component expresses interest in attachment, and disconnected when the
|
|
8
|
+
* last interested component is stopped or collected. The observer never inspects
|
|
9
|
+
* mutation records: it is only a "the tree changed" tick. On each batch it walks
|
|
10
|
+
* the tracked components and asks each to compare its root's isConnected against
|
|
11
|
+
* its own recorded attachment state (Jqhtml_Component._attachment_check()). Cost is
|
|
12
|
+
* O(tracked components) per mutation batch, independent of how much of the page
|
|
13
|
+
* changed, and zero when nothing has opted in.
|
|
14
|
+
*
|
|
15
|
+
* Because MutationObserver delivers per microtask, a node moved synchronously
|
|
16
|
+
* (remove + insert in one task, e.g. appendTo) never shows a transition. Only a
|
|
17
|
+
* component whose root is actually off-document when the batch flushes is detached.
|
|
18
|
+
*
|
|
19
|
+
* Tracked components are held by WeakRef. A component that is removed from the
|
|
20
|
+
* document natively (never stopped) and then dropped by everything else is therefore
|
|
21
|
+
* collectable: the tracker pins nothing. Dead refs are pruned on the next batch.
|
|
22
|
+
*/
|
|
23
|
+
/** Start watching a component's root for attach/detach transitions. Idempotent. */
|
|
24
|
+
export declare function track_attachment(component: any): void;
|
|
25
|
+
/** Stop watching a component (called from _stop()). Idempotent. */
|
|
26
|
+
export declare function untrack_attachment(component: any): void;
|
|
27
|
+
/** Number of components currently tracked (diagnostics / tests). */
|
|
28
|
+
export declare function tracked_attachment_count(): number;
|
|
29
|
+
/** Whether the document observer is currently installed (diagnostics / tests). */
|
|
30
|
+
export declare function attachment_observer_active(): boolean;
|
|
31
|
+
//# sourceMappingURL=dom-attachment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-attachment.d.ts","sourceRoot":"","sources":["../src/dom-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AA+BH,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI,CAMrD;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI,CAMvD;AAED,oEAAoE;AACpE,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,kFAAkF;AAClF,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD"}
|
package/dist/index.cjs
CHANGED
|
@@ -218,12 +218,17 @@ class LifecycleManager {
|
|
|
218
218
|
return LifecycleManager.instance;
|
|
219
219
|
}
|
|
220
220
|
constructor() {
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
221
|
+
// Components stop explicitly via:
|
|
222
|
+
// 1. Parent component re-render (stops descendants before clearing innerHTML)
|
|
223
|
+
// 2. Any jQuery removal that reaches jQuery.cleanData (.remove(), .empty(),
|
|
224
|
+
// .html(x), .text(x), .replaceWith(), .replaceAll()) - hooked in jquery-plugin.ts
|
|
225
225
|
// 3. Explicit .stop() call
|
|
226
|
-
//
|
|
226
|
+
// Native DOM removal is NOT observed for stop; 'detach' (dom-attachment.ts) is the
|
|
227
|
+
// signal for that. Moving a component within the document never stops it.
|
|
228
|
+
//
|
|
229
|
+
// There is deliberately no registry of live components here. The DOM (via
|
|
230
|
+
// element.data('_component')) is the only owner; a registry would pin every
|
|
231
|
+
// component that left the document without being stopped.
|
|
227
232
|
}
|
|
228
233
|
/**
|
|
229
234
|
* Boot a component - run its full lifecycle
|
|
@@ -234,7 +239,6 @@ class LifecycleManager {
|
|
|
234
239
|
* - _load_render_only: on_create + render + on_load + re-render. No on_render, no on_loaded, no on_ready.
|
|
235
240
|
*/
|
|
236
241
|
async boot_component(component) {
|
|
237
|
-
this.active_components.add(component);
|
|
238
242
|
try {
|
|
239
243
|
// Create phase - SYNCHRONOUS, runs BEFORE first render
|
|
240
244
|
component.create();
|
|
@@ -395,12 +399,6 @@ class LifecycleManager {
|
|
|
395
399
|
}
|
|
396
400
|
}
|
|
397
401
|
}
|
|
398
|
-
/**
|
|
399
|
-
* Unregister a component (called on destroy)
|
|
400
|
-
*/
|
|
401
|
-
unregister_component(component) {
|
|
402
|
-
this.active_components.delete(component);
|
|
403
|
-
}
|
|
404
402
|
}
|
|
405
403
|
|
|
406
404
|
/**
|
|
@@ -1619,7 +1617,7 @@ function serialize_for_cache_key(value) {
|
|
|
1619
1617
|
const class_registry = Object.create(null);
|
|
1620
1618
|
// Markers for serialized class instances (unique to avoid collisions with user data)
|
|
1621
1619
|
// Replaced at build time by rollup (see rollup.config.js).
|
|
1622
|
-
const CORE_VERSION = '2.3.
|
|
1620
|
+
const CORE_VERSION = '2.3.72';
|
|
1623
1621
|
const CLASS_MARKER = '__jqhtml_class__';
|
|
1624
1622
|
const PROPS_MARKER = '__jqhtml_props__';
|
|
1625
1623
|
/**
|
|
@@ -3180,6 +3178,85 @@ async function execute_on_load_detached(component, dedup_key = null) {
|
|
|
3180
3178
|
};
|
|
3181
3179
|
}
|
|
3182
3180
|
|
|
3181
|
+
/**
|
|
3182
|
+
* JQHTML DOM Attachment Tracking
|
|
3183
|
+
*
|
|
3184
|
+
* Backs the component 'attach' / 'detach' lifecycle events.
|
|
3185
|
+
*
|
|
3186
|
+
* One document-level MutationObserver (childList + subtree) is installed the first
|
|
3187
|
+
* time any component expresses interest in attachment, and disconnected when the
|
|
3188
|
+
* last interested component is stopped or collected. The observer never inspects
|
|
3189
|
+
* mutation records: it is only a "the tree changed" tick. On each batch it walks
|
|
3190
|
+
* the tracked components and asks each to compare its root's isConnected against
|
|
3191
|
+
* its own recorded attachment state (Jqhtml_Component._attachment_check()). Cost is
|
|
3192
|
+
* O(tracked components) per mutation batch, independent of how much of the page
|
|
3193
|
+
* changed, and zero when nothing has opted in.
|
|
3194
|
+
*
|
|
3195
|
+
* Because MutationObserver delivers per microtask, a node moved synchronously
|
|
3196
|
+
* (remove + insert in one task, e.g. appendTo) never shows a transition. Only a
|
|
3197
|
+
* component whose root is actually off-document when the batch flushes is detached.
|
|
3198
|
+
*
|
|
3199
|
+
* Tracked components are held by WeakRef. A component that is removed from the
|
|
3200
|
+
* document natively (never stopped) and then dropped by everything else is therefore
|
|
3201
|
+
* collectable: the tracker pins nothing. Dead refs are pruned on the next batch.
|
|
3202
|
+
*/
|
|
3203
|
+
const tracked = new Set();
|
|
3204
|
+
const ref_of = new WeakMap();
|
|
3205
|
+
let observer = null;
|
|
3206
|
+
function on_mutation() {
|
|
3207
|
+
for (const ref of tracked) {
|
|
3208
|
+
const component = ref.deref();
|
|
3209
|
+
if (!component || component._stopped) {
|
|
3210
|
+
tracked.delete(ref);
|
|
3211
|
+
continue;
|
|
3212
|
+
}
|
|
3213
|
+
component._attachment_check();
|
|
3214
|
+
}
|
|
3215
|
+
if (tracked.size === 0)
|
|
3216
|
+
disconnect();
|
|
3217
|
+
}
|
|
3218
|
+
function connect() {
|
|
3219
|
+
if (observer !== null)
|
|
3220
|
+
return;
|
|
3221
|
+
if (typeof MutationObserver === 'undefined' || typeof document === 'undefined')
|
|
3222
|
+
return;
|
|
3223
|
+
observer = new MutationObserver(on_mutation);
|
|
3224
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
3225
|
+
}
|
|
3226
|
+
function disconnect() {
|
|
3227
|
+
if (observer === null)
|
|
3228
|
+
return;
|
|
3229
|
+
observer.disconnect();
|
|
3230
|
+
observer = null;
|
|
3231
|
+
}
|
|
3232
|
+
/** Start watching a component's root for attach/detach transitions. Idempotent. */
|
|
3233
|
+
function track_attachment(component) {
|
|
3234
|
+
if (ref_of.has(component))
|
|
3235
|
+
return;
|
|
3236
|
+
const ref = new WeakRef(component);
|
|
3237
|
+
ref_of.set(component, ref);
|
|
3238
|
+
tracked.add(ref);
|
|
3239
|
+
connect();
|
|
3240
|
+
}
|
|
3241
|
+
/** Stop watching a component (called from _stop()). Idempotent. */
|
|
3242
|
+
function untrack_attachment(component) {
|
|
3243
|
+
const ref = ref_of.get(component);
|
|
3244
|
+
if (!ref)
|
|
3245
|
+
return;
|
|
3246
|
+
ref_of.delete(component);
|
|
3247
|
+
tracked.delete(ref);
|
|
3248
|
+
if (tracked.size === 0)
|
|
3249
|
+
disconnect();
|
|
3250
|
+
}
|
|
3251
|
+
/** Number of components currently tracked (diagnostics / tests). */
|
|
3252
|
+
function tracked_attachment_count() {
|
|
3253
|
+
return tracked.size;
|
|
3254
|
+
}
|
|
3255
|
+
/** Whether the document observer is currently installed (diagnostics / tests). */
|
|
3256
|
+
function attachment_observer_active() {
|
|
3257
|
+
return observer !== null;
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3183
3260
|
/**
|
|
3184
3261
|
* JQHTML Component Cache Integration
|
|
3185
3262
|
*
|
|
@@ -4088,6 +4165,12 @@ class Jqhtml_Component {
|
|
|
4088
4165
|
this.__load_gates = []; // Pending gates (cleared once the first load begins)
|
|
4089
4166
|
this.__first_load_started = false; // Latch: true once the first load phase begins (enforces one-shot + throw-after)
|
|
4090
4167
|
this.__gate_resume = null; // Resolver to release an in-progress gate wait early (reload/refresh/stop)
|
|
4168
|
+
// DOM attachment state machine - see _attachment_check() / dom-attachment.ts.
|
|
4169
|
+
// _attached is the component's OWN belief about whether it is in the document;
|
|
4170
|
+
// it flips at most once per direction, and 'attach' / 'detach' fire only on a flip.
|
|
4171
|
+
this._attached = false;
|
|
4172
|
+
this._attachment_checked = false; // First check has run (after the first 'ready' dispatch)
|
|
4173
|
+
this._attachment_tracking = false; // Something is interested: on_attach/on_detach overridden, or .on('attach'|'detach')
|
|
4091
4174
|
// Detect custom on_load() override immediately
|
|
4092
4175
|
// Prototype chain is set up before constructor runs, so this.on_load
|
|
4093
4176
|
// correctly resolves to child class override if one exists
|
|
@@ -4161,6 +4244,12 @@ class Jqhtml_Component {
|
|
|
4161
4244
|
// Initialize this.state as an empty object (convention for arbitrary component state)
|
|
4162
4245
|
// No special meaning to framework - mutable anywhere, not cached, not frozen
|
|
4163
4246
|
this.state = {};
|
|
4247
|
+
// Overriding on_attach()/on_detach() is interest in attachment tracking. Tracking
|
|
4248
|
+
// itself only starts after the first 'ready' dispatch (see _attachment_check()).
|
|
4249
|
+
if (this.on_attach !== Jqhtml_Component.prototype.on_attach ||
|
|
4250
|
+
this.on_detach !== Jqhtml_Component.prototype.on_detach) {
|
|
4251
|
+
this._attachment_tracking = true;
|
|
4252
|
+
}
|
|
4164
4253
|
this._log_lifecycle('construct', 'complete');
|
|
4165
4254
|
}
|
|
4166
4255
|
/**
|
|
@@ -4174,7 +4263,9 @@ class Jqhtml_Component {
|
|
|
4174
4263
|
on_render: 'Use render() to trigger a re-render.',
|
|
4175
4264
|
on_load: 'Use reload() to refresh data.',
|
|
4176
4265
|
on_ready: 'Called automatically when ready.',
|
|
4177
|
-
on_stop: 'Use stop() to stop the component.'
|
|
4266
|
+
on_stop: 'Use stop() to stop the component.',
|
|
4267
|
+
on_attach: 'Called automatically when the component enters the document.',
|
|
4268
|
+
on_detach: 'Called automatically when the component leaves the document.'
|
|
4178
4269
|
};
|
|
4179
4270
|
const impls = {};
|
|
4180
4271
|
const self = this;
|
|
@@ -5528,8 +5619,13 @@ class Jqhtml_Component {
|
|
|
5528
5619
|
// stopped children into the parent's registry.
|
|
5529
5620
|
this._log_lifecycle('destroy', 'start');
|
|
5530
5621
|
this.$.addClass('_Component_Stopped');
|
|
5531
|
-
//
|
|
5532
|
-
|
|
5622
|
+
// A component that believes it is attached is detached first, so 'detach' always
|
|
5623
|
+
// precedes 'stop' and subscribers can rely on strict attach/detach alternation.
|
|
5624
|
+
// A component that never fired 'attach' (stopped before ready, or born and kept
|
|
5625
|
+
// off-document) fires no 'detach' either. Either way it leaves the tracker.
|
|
5626
|
+
if (this._attached)
|
|
5627
|
+
this._transition_attachment(false);
|
|
5628
|
+
untrack_attachment(this);
|
|
5533
5629
|
// Call user's on_stop() hook with authorization (sync) - only when actually
|
|
5534
5630
|
// overridden, since _call_lifecycle_sync() on the base no-op costs per child
|
|
5535
5631
|
// on every re-render.
|
|
@@ -5574,6 +5670,89 @@ class Jqhtml_Component {
|
|
|
5574
5670
|
on_loaded() { } // Override to process loaded data (e.g., clone this.data to this.state)
|
|
5575
5671
|
async on_ready() { }
|
|
5576
5672
|
on_stop() { }
|
|
5673
|
+
on_attach() { } // Sync. Root entered the document (first checked after 'ready')
|
|
5674
|
+
on_detach() { } // Sync. Root left the document, or the component is being stopped while attached
|
|
5675
|
+
// -------------------------------------------------------------------------
|
|
5676
|
+
// DOM attachment state machine
|
|
5677
|
+
// -------------------------------------------------------------------------
|
|
5678
|
+
/**
|
|
5679
|
+
* Whether this component currently considers itself attached to the document.
|
|
5680
|
+
*
|
|
5681
|
+
* This is the component's recorded state, not a live isConnected read: it is
|
|
5682
|
+
* false until the first check after 'ready', and from then on flips only when a
|
|
5683
|
+
* transition is observed (or on stop()). It is exactly the state 'attach' and
|
|
5684
|
+
* 'detach' have reported.
|
|
5685
|
+
*/
|
|
5686
|
+
is_attached() {
|
|
5687
|
+
return this._attached;
|
|
5688
|
+
}
|
|
5689
|
+
/**
|
|
5690
|
+
* Flip the recorded state and fire the matching hook + event. The opposite
|
|
5691
|
+
* sticky event is invalidated first, so a late subscriber only ever sees the
|
|
5692
|
+
* current state. Hook errors are isolated; the event still fires.
|
|
5693
|
+
* @private
|
|
5694
|
+
*/
|
|
5695
|
+
_transition_attachment(attached) {
|
|
5696
|
+
this._attached = attached;
|
|
5697
|
+
const event_name = attached ? 'attach' : 'detach';
|
|
5698
|
+
const hook_name = attached ? 'on_attach' : 'on_detach';
|
|
5699
|
+
this.invalidate(attached ? 'detach' : 'attach');
|
|
5700
|
+
if (this[hook_name] !== Jqhtml_Component.prototype[hook_name]) {
|
|
5701
|
+
try {
|
|
5702
|
+
const result = this._call_lifecycle_sync(hook_name);
|
|
5703
|
+
if (result && typeof result.then === 'function') {
|
|
5704
|
+
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from ${hook_name}(). ` +
|
|
5705
|
+
`${hook_name}() must be synchronous code. Remove 'async' from the function declaration.`);
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
catch (error) {
|
|
5709
|
+
console.error(`[JQHTML] Error in ${hook_name}() for ${this.component_name()}:`, error);
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
this.trigger(event_name);
|
|
5713
|
+
}
|
|
5714
|
+
/**
|
|
5715
|
+
* Compare the root's live connectivity with the recorded state and transition
|
|
5716
|
+
* on a difference. Idempotent. Called by the document observer on every
|
|
5717
|
+
* mutation batch for tracked components, and directly by the framework after
|
|
5718
|
+
* 'ready' and when interest is registered late.
|
|
5719
|
+
* @private
|
|
5720
|
+
*/
|
|
5721
|
+
_attachment_check() {
|
|
5722
|
+
if (this._stopped)
|
|
5723
|
+
return;
|
|
5724
|
+
const connected = !!this.$[0].isConnected;
|
|
5725
|
+
if (connected !== this._attached)
|
|
5726
|
+
this._transition_attachment(connected);
|
|
5727
|
+
}
|
|
5728
|
+
/**
|
|
5729
|
+
* The first check, run once the 'ready' handlers have completed. Fires 'attach'
|
|
5730
|
+
* if the component was born attached, even with no listener, so a later
|
|
5731
|
+
* .on('attach') gets the sticky fire. Deferred interest starts tracking here.
|
|
5732
|
+
* @private
|
|
5733
|
+
*/
|
|
5734
|
+
_attachment_first_check() {
|
|
5735
|
+
const first = !this._attachment_checked;
|
|
5736
|
+
this._attachment_checked = true;
|
|
5737
|
+
this._attachment_check();
|
|
5738
|
+
if (first && this._attachment_tracking)
|
|
5739
|
+
track_attachment(this);
|
|
5740
|
+
}
|
|
5741
|
+
/**
|
|
5742
|
+
* Register interest in attach/detach. Before the first ready check this only
|
|
5743
|
+
* records the interest; after it, tracking starts immediately and a check runs
|
|
5744
|
+
* at once, so a late subscriber on a natively-removed component sees 'detach'.
|
|
5745
|
+
* @private
|
|
5746
|
+
*/
|
|
5747
|
+
_watch_attachment() {
|
|
5748
|
+
if (this._attachment_tracking)
|
|
5749
|
+
return;
|
|
5750
|
+
this._attachment_tracking = true;
|
|
5751
|
+
if (this._attachment_checked && !this._stopped) {
|
|
5752
|
+
track_attachment(this);
|
|
5753
|
+
this._attachment_check();
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5577
5756
|
/**
|
|
5578
5757
|
* Called with the viewport width in CSS pixels:
|
|
5579
5758
|
* - after every on_render()
|
|
@@ -5638,6 +5817,8 @@ class Jqhtml_Component {
|
|
|
5638
5817
|
* @see component-events.ts for full documentation
|
|
5639
5818
|
*/
|
|
5640
5819
|
on(event_name, callback) {
|
|
5820
|
+
if (event_name === 'attach' || event_name === 'detach')
|
|
5821
|
+
this._watch_attachment();
|
|
5641
5822
|
return event_on(this, event_name, callback);
|
|
5642
5823
|
}
|
|
5643
5824
|
/**
|
|
@@ -5646,14 +5827,24 @@ class Jqhtml_Component {
|
|
|
5646
5827
|
* @see component-events.ts for full documentation
|
|
5647
5828
|
*/
|
|
5648
5829
|
once(event_name, callback) {
|
|
5830
|
+
if (event_name === 'attach' || event_name === 'detach')
|
|
5831
|
+
this._watch_attachment();
|
|
5649
5832
|
return event_once(this, event_name, callback);
|
|
5650
5833
|
}
|
|
5651
5834
|
/**
|
|
5652
5835
|
* Trigger a lifecycle event - delegates to component-events.ts
|
|
5653
5836
|
* @see component-events.ts for full documentation
|
|
5837
|
+
*
|
|
5838
|
+
* 'ready' is special: once its handlers have run, the component runs its first
|
|
5839
|
+
* attachment check (and every later one, idempotently). Doing it here rather than
|
|
5840
|
+
* at each of the call sites that trigger 'ready' guarantees the order
|
|
5841
|
+
* on_ready() -> 'ready' handlers -> 'attach', so everything on_ready() set up is
|
|
5842
|
+
* complete before anyone learns the component is in the document.
|
|
5654
5843
|
*/
|
|
5655
5844
|
trigger(event_name, data) {
|
|
5656
5845
|
event_trigger(this, event_name, data);
|
|
5846
|
+
if (event_name === 'ready' && !this._stopped)
|
|
5847
|
+
this._attachment_first_check();
|
|
5657
5848
|
}
|
|
5658
5849
|
/**
|
|
5659
5850
|
* Check if any callbacks are registered for a given event
|
|
@@ -7087,9 +7278,42 @@ function init_jquery_plugin(jQuery) {
|
|
|
7087
7278
|
return jQuery(results);
|
|
7088
7279
|
};
|
|
7089
7280
|
// Store original jQuery methods
|
|
7090
|
-
const originalEmpty = jQuery.fn.empty;
|
|
7091
7281
|
const originalHtml = jQuery.fn.html;
|
|
7092
|
-
|
|
7282
|
+
// ---------------------------------------------------------------------------
|
|
7283
|
+
// Stop components on every jQuery-driven removal by hooking jQuery.cleanData.
|
|
7284
|
+
//
|
|
7285
|
+
// jQuery routes .remove(), .empty(), .html(x), .text(x), .replaceWith() and
|
|
7286
|
+
// .replaceAll() through jQuery.cleanData(elems) to drop the data store and
|
|
7287
|
+
// handlers of every element about to leave the tree - and `elems` is already the
|
|
7288
|
+
// flattened subtree, in document order (parent before child). Stopping here is
|
|
7289
|
+
// therefore one classList check per removed element, no extra traversal, and
|
|
7290
|
+
// covers every jQuery removal path at once. It is the same hook jQuery UI's
|
|
7291
|
+
// widget factory uses for the same reason.
|
|
7292
|
+
//
|
|
7293
|
+
// It MUST be installed on the original jQuery object: jQuery's own code calls
|
|
7294
|
+
// cleanData through its closure-local reference, not through our wrapper. The
|
|
7295
|
+
// wrapper's copied static is updated below so both names agree.
|
|
7296
|
+
//
|
|
7297
|
+
// .detach() does NOT call cleanData, by jQuery's own design (it keeps data for
|
|
7298
|
+
// reinsertion), and native DOM removal (el.remove(), innerHTML = '') bypasses
|
|
7299
|
+
// jQuery entirely. Neither stops a component; the 'detach' lifecycle event is
|
|
7300
|
+
// the signal for those. Moving a component (.appendTo) is not a removal.
|
|
7301
|
+
// ---------------------------------------------------------------------------
|
|
7302
|
+
const originalCleanData = _jqhtml_original_jquery.cleanData;
|
|
7303
|
+
const cleanDataWithComponentStop = function (elems) {
|
|
7304
|
+
for (let i = 0; i < elems.length; i++) {
|
|
7305
|
+
const el = elems[i];
|
|
7306
|
+
if (el.nodeType !== 1 || !el.classList.contains('Component'))
|
|
7307
|
+
continue;
|
|
7308
|
+
const component = _jqhtml_original_jquery.data(el, '_component');
|
|
7309
|
+
if (component && !component._stopped) {
|
|
7310
|
+
component._stop(); // Not recursive: every descendant is in elems too
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
return originalCleanData(elems);
|
|
7314
|
+
};
|
|
7315
|
+
_jqhtml_original_jquery.cleanData = cleanDataWithComponentStop;
|
|
7316
|
+
jQuery.cleanData = cleanDataWithComponentStop;
|
|
7093
7317
|
// ---------------------------------------------------------------------------
|
|
7094
7318
|
// Guard against hand-creating elements with data-sid via jQuery.
|
|
7095
7319
|
//
|
|
@@ -7146,53 +7370,15 @@ function init_jquery_plugin(jQuery) {
|
|
|
7146
7370
|
` • <div class="MyComponent__${sid}"> → .find('.MyComponent__${sid}')`);
|
|
7147
7371
|
};
|
|
7148
7372
|
/**
|
|
7149
|
-
* Override jQuery.fn.
|
|
7150
|
-
*
|
|
7151
|
-
*/
|
|
7152
|
-
jQuery.fn.empty = function () {
|
|
7153
|
-
return this.each(function () {
|
|
7154
|
-
// Stop all child components before clearing DOM
|
|
7155
|
-
jQuery(this).find('.Component').each(function () {
|
|
7156
|
-
const component = jQuery(this).data('_component');
|
|
7157
|
-
if (component && !component._stopped) {
|
|
7158
|
-
component._stop(); // Stop just the component, DOM will be cleared anyway
|
|
7159
|
-
}
|
|
7160
|
-
});
|
|
7161
|
-
// Call original empty
|
|
7162
|
-
originalEmpty.call(jQuery(this));
|
|
7163
|
-
});
|
|
7164
|
-
};
|
|
7165
|
-
/**
|
|
7166
|
-
* Override jQuery.fn.html() to stop child components when setting new content
|
|
7167
|
-
* When used as setter, calls .empty() first to properly cleanup components
|
|
7373
|
+
* Override jQuery.fn.html() setter to reject hand-written data-sid before the
|
|
7374
|
+
* DOM is mutated. Component stopping is handled by the cleanData hook above.
|
|
7168
7375
|
*/
|
|
7169
7376
|
jQuery.fn.html = function (value) {
|
|
7170
|
-
// Getter - just pass through
|
|
7171
7377
|
if (arguments.length === 0) {
|
|
7172
7378
|
return originalHtml.call(this);
|
|
7173
7379
|
}
|
|
7174
|
-
// Reject hand-written data-sid before mutating the DOM
|
|
7175
7380
|
assert_no_manual_sid(value, '.html()');
|
|
7176
|
-
|
|
7177
|
-
return this.each(function () {
|
|
7178
|
-
jQuery(this).empty();
|
|
7179
|
-
originalHtml.call(jQuery(this), value);
|
|
7180
|
-
});
|
|
7181
|
-
};
|
|
7182
|
-
/**
|
|
7183
|
-
* Override jQuery.fn.text() to stop child components when setting new content
|
|
7184
|
-
* When used as setter, calls .empty() first to properly cleanup components
|
|
7185
|
-
*/
|
|
7186
|
-
jQuery.fn.text = function (value) {
|
|
7187
|
-
// Getter - just pass through
|
|
7188
|
-
if (arguments.length === 0) {
|
|
7189
|
-
return originalText.call(this);
|
|
7190
|
-
}
|
|
7191
|
-
// Setter - empty first (which stops components), then set content
|
|
7192
|
-
return this.each(function () {
|
|
7193
|
-
jQuery(this).empty();
|
|
7194
|
-
originalText.call(jQuery(this), value);
|
|
7195
|
-
});
|
|
7381
|
+
return originalHtml.call(this, value);
|
|
7196
7382
|
};
|
|
7197
7383
|
// Store original jQuery.fn.on
|
|
7198
7384
|
const originalOn = jQuery.fn.on;
|
|
@@ -7334,7 +7520,7 @@ function init(jQuery, config) {
|
|
|
7334
7520
|
}
|
|
7335
7521
|
}
|
|
7336
7522
|
// Version - will be replaced during build with actual version from package.json
|
|
7337
|
-
const version = '2.3.
|
|
7523
|
+
const version = '2.3.72';
|
|
7338
7524
|
// Default export with all functionality
|
|
7339
7525
|
const jqhtml = {
|
|
7340
7526
|
// Core
|
|
@@ -7449,7 +7635,11 @@ const jqhtml = {
|
|
|
7449
7635
|
get_captured_data,
|
|
7450
7636
|
stop_data_capture,
|
|
7451
7637
|
set_preload_data,
|
|
7452
|
-
clear_preload_data
|
|
7638
|
+
clear_preload_data,
|
|
7639
|
+
// Attachment tracking diagnostics - how many components are watched for
|
|
7640
|
+
// attach/detach and whether the document observer is currently installed
|
|
7641
|
+
tracked_attachment_count,
|
|
7642
|
+
attachment_observer_active
|
|
7453
7643
|
};
|
|
7454
7644
|
// Auto-register on window for browser environments
|
|
7455
7645
|
// This is REQUIRED for compiled templates which use window.jqhtml.register_template()
|
|
@@ -7488,6 +7678,7 @@ exports.LifecycleManager = LifecycleManager;
|
|
|
7488
7678
|
exports.Load_Coordinator = Load_Coordinator;
|
|
7489
7679
|
exports.add_object_printer = add_object_printer;
|
|
7490
7680
|
exports.applyDebugDelay = applyDebugDelay;
|
|
7681
|
+
exports.attachment_observer_active = attachment_observer_active;
|
|
7491
7682
|
exports.boot = boot;
|
|
7492
7683
|
exports.clear_preload_data = clear_preload_data;
|
|
7493
7684
|
exports.configure = configure;
|
|
@@ -7527,5 +7718,6 @@ exports.register_template = register_template;
|
|
|
7527
7718
|
exports.set_preload_data = set_preload_data;
|
|
7528
7719
|
exports.start_data_capture = start_data_capture;
|
|
7529
7720
|
exports.stop_data_capture = stop_data_capture;
|
|
7721
|
+
exports.tracked_attachment_count = tracked_attachment_count;
|
|
7530
7722
|
exports.version = version;
|
|
7531
7723
|
//# sourceMappingURL=index.cjs.map
|