Skip to content

Commit ae3a71c

Browse files
committed
Document to Target
1 parent b7f7ce5 commit ae3a71c

File tree

1 file changed

+82
-62
lines changed

1 file changed

+82
-62
lines changed

behaviours/behaviour.js

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
//
2222
// (Note that calls to `connected` and `disconnected` are delayed by `MutationObserver` which is what we use to track changes to the document)
2323
//
24-
// In this version of the library, behaviours are enabled on a per-document basis.
24+
// In this version of the library, behaviours are enabled on a per-target basis.
2525
// Call `enableBehaviours(document)` to enable behaviour handling for that document.
2626
// It is not necessary to call `enableBehaviours(document)` for the main document since it will be enabled automatically.
27-
// You can still call it if you want to control when the document behaviours are updated, or you can call `disableBehaviours(document)` if you
27+
// You can still call it if you want to control when the behaviours are updated, or you can call `disableBehaviours(document)` if you
2828
// prefer behaviours not to be enabled for the main document.
2929
//
30+
// You can also pass an element to `enableBehaviours` to limit the scope to that element.
31+
//
3032
// You can get a behaviour from an element if you know the type (or base type) of the behavior:
3133
//
3234
// const behaviour = getBehaviour(element, MyBehaviour);
@@ -194,11 +196,14 @@ function* chain(current) {
194196
}
195197

196198
/**
197-
* Tests whether the base object is in the prototype chain of the derived object
199+
* Tests whether the `base` object is in the prototype chain of the `derived` object
198200
* @param { object } derived
199201
* @param { object } base
200202
*/
201-
function isBase(derived, base) {
203+
function isBase({ derived, base }) {
204+
if (derived === undefined || base === undefined) {
205+
throw "Invalid argument to isBase";
206+
}
202207
for (const link of chain(derived)) {
203208
if (link === base) {
204209
return true;
@@ -281,7 +286,7 @@ export function getBehaviour(element, behaviourType) {
281286
// If we are given MyBaseBehaviour and we have stored MyDerivedBehaviour,
282287
// we still need to find the behaviour and return it
283288
for (const [behaviourType_, behavior_] of instances.entries()) {
284-
if (isBase(behaviourType_, behaviourType)) {
289+
if (isBase({ derived: behaviourType_, base: behaviourType })) {
285290
return behavior_;
286291
}
287292
}
@@ -291,7 +296,12 @@ export function getBehaviour(element, behaviourType) {
291296

292297
/** @typedef { { key: BehaviourKey, elementType: typeof HTMLElement, behaviourType: typeof Behaviour, existingBehaviourType: typeof Behaviour } } Warning */
293298

294-
class BehaviourRegistry {
299+
/**
300+
* A `Target` is a document or element in which behaviours have been enabled
301+
* @typedef { Node } Target
302+
* */
303+
304+
export class BehaviourRegistry {
295305

296306
/**
297307
* This contains the behaviours directly applied to a particular element type.
@@ -302,62 +312,70 @@ class BehaviourRegistry {
302312
elementTypeToBehaviourRecord = new Map();
303313

304314
/**
305-
* Stores documents that we're watching for changes
306-
* @type Map<Document, { registry: BehaviourRegistry, controller: AbortController } >
315+
* Stores targets that we're watching for changes
316+
* @type Map<Target, { registry: BehaviourRegistry, controller: AbortController } >
307317
* */
308-
documentToRegistry = new Map();
318+
targetToRegistry = new Map();
309319

310320
/**
311-
* Documents that need to be re-examined for behaviours
312-
* @type Document[]
321+
* Targets that need to be re-examined for behaviours
322+
* @type Target[]
313323
*/
314-
documentQueue = [];
324+
queue = [];
315325

316326
/**
317327
* We only ensure the main document is enabled once on the first call to `register`
318328
* so that callers can successfully disable this document without us re-enabling it
319329
*/
320-
isMainDocumentHandled = false;
330+
shouldHandleMainDocument = false;
331+
332+
constructor(shouldHandleMainDocument = false) {
333+
this.shouldHandleMainDocument = shouldHandleMainDocument;
334+
}
321335

322336
/**
323-
* Add a document to the queue - the entire document will be examined for elements/attributes that need to be connected to behaviours.
324-
* The processing of the document happens in a microtask.
325-
* @param { Document } document
337+
* Add a target to the queue - the target node will be examined for elements/attributes that need to be connected to behaviours.
338+
* The processing of the target happens in a microtask.
339+
* @param { Target } target
326340
* */
327-
addToQueue(document) {
328-
if (!this.documentQueue.includes(document)) {
329-
this.documentQueue.push(document);
341+
addToQueue(target) {
342+
if (!this.queue.includes(target)) {
343+
this.queue.push(target);
330344
this.needsUpdate = true;
331345
}
332346
}
333347

334-
updateDocument(document) {
335-
const existing = this.documentToRegistry.get(document);
348+
/**
349+
*
350+
* @param { Target } target
351+
*/
352+
updateTarget(target) {
353+
const existing = this.targetToRegistry.get(target);
336354

337355
if (existing === undefined) {
338-
const controller = listenToElement(document.body, (mutations, observer) => {
356+
const controller = listenToElement(target, (mutations, observer) => {
339357
for (const mutation of mutations) {
340358
this.handleMutation(mutation);
341359
}
342360
});
343361

344-
this.documentToRegistry.set(document, { registry: this, controller });
362+
this.targetToRegistry.set(target, { registry: this, controller });
345363
}
346364

347-
this.connect(document.body.querySelectorAll("*"));
365+
this.connect(target.querySelectorAll("*"));
348366
}
349367

350368
/**
351-
* Processes all the documents in the queue.
369+
* Processes all the targets in the queue.
352370
* Adds mutation observation if necessary and
353-
* examines all existing document content to attach behaviours if necessary.
371+
* examines all existing target content to attach behaviours if necessary.
354372
*/
355373
update() {
356-
const documents = this.documentQueue;
357-
this.documentQueue = [];
374+
const targets = this.queue;
375+
this.queue = [];
358376

359-
for (const document of documents) {
360-
this.updateDocument(document);
377+
for (const target of targets) {
378+
this.updateTarget(target);
361379
}
362380
}
363381

@@ -421,15 +439,15 @@ class BehaviourRegistry {
421439

422440
// For the first registration, we add the main document to the queue so that users cannot forget to call enable on it
423441
// We only do this once so that users can choose to disable the main document
424-
if (!this.isMainDocumentHandled) {
425-
this.isMainDocumentHandled = true;
426-
this.addToQueue(document);
442+
if (this.shouldHandleMainDocument) {
443+
this.shouldHandleMainDocument = false;
444+
this.addToQueue(globalThis.document);
427445
}
428446

429-
// If behaviours get registered after a document is enabled,
430-
// we need to update existing documents
431-
for (const document of this.documentToRegistry.keys()) {
432-
this.addToQueue(document);
447+
// If behaviours get registered after a target is enabled,
448+
// we need to update existing targets
449+
for (const target of this.targetToRegistry.keys()) {
450+
this.addToQueue(target);
433451
}
434452

435453
return { warnings };
@@ -487,61 +505,63 @@ class BehaviourRegistry {
487505
}
488506

489507
/**
490-
* Starts watching the document and connecting behaviours to relevant elements
491-
* @param { Document } document
508+
* Starts watching the target and connecting behaviours to relevant elements
509+
* @param { Target } target
492510
*/
493-
enable(document) {
494-
// Here we add the specified document to the queue then immediately process the queue
511+
enable(target) {
512+
// Here we add the specified target to the queue then immediately process the queue
495513
// to ensure that all routes are going through the same code paths.
496-
this.addToQueue(document);
514+
this.addToQueue(target);
497515
this.update();
498516
}
499517

500518
/**
501-
* Stops watching the document. Does not disconnect already connected behaviours.
502-
* @param { Document } document
519+
* Stops watching the target. Does not disconnect already connected behaviours.
520+
* @param { Target } target
503521
*/
504-
disable(document) {
505-
if (document === globalThis.document) {
522+
disable(target) {
523+
if (target === globalThis.document) {
506524
// Disable main document handling by saying it's already done
507525
// This code means that users can disable the main document before any registrations.
508526
// Without this code, users would have to ensure that disable is called after the first registration.
509-
this.isMainDocumentHandled = true;
527+
this.shouldHandleMainDocument = false;
510528
}
511529

512-
// Remove this document from the queue if it's there
513-
const index = this.documentQueue.indexOf(document);
530+
// Remove this target from the queue if it's there
531+
const index = this.queue.indexOf(target);
514532
if (index >= 0) {
515-
this.documentQueue.splice(index, 1);
533+
this.queue.splice(index, 1);
516534
}
517535

518-
// Stop listening to this document if we've started listening
519-
const existing = this.documentToRegistry.get(document);
536+
// Stop listening to this target if we've started listening
537+
const existing = this.targetToRegistry.get(target);
520538
if (existing !== undefined) {
521539
existing.controller.abort();
522-
this.documentToRegistry.delete(document);
540+
this.targetToRegistry.delete(target);
523541
}
524542
}
525543
}
526544

527545
addMicrotaskProperty(BehaviourRegistry, "update");
528546

529-
const customBehaviours = new BehaviourRegistry();
547+
// The global behaviour registry has main document handling turned on
548+
// Other behaviour registries default to main document handling being off
549+
export const customBehaviours = new BehaviourRegistry(true);
530550

531551
/**
532-
* Starts watching the document and connecting behaviours to relevant elements
533-
* @param { Document } document
552+
* Starts watching the target and connecting behaviours to relevant elements
553+
* @param { Target } target
534554
*/
535-
export function enableBehaviours(document) {
536-
customBehaviours.enable(document);
555+
export function enableBehaviours(target) {
556+
customBehaviours.enable(target);
537557
}
538558

539559
/**
540-
* Stops watching the document. Does not disconnect already connected behaviours.
541-
* @param { Document } document
560+
* Stops watching the target. Does not disconnect already connected behaviours.
561+
* @param { Target } target
542562
*/
543-
export function disableBehaviours(document) {
544-
customBehaviours.disable(document);
563+
export function disableBehaviours(target) {
564+
customBehaviours.disable(target);
545565
}
546566

547567
/**

0 commit comments

Comments
 (0)