Skip to content

Commit b7f7ce5

Browse files
committed
types comments universal keys
1 parent f47bfac commit b7f7ce5

File tree

1 file changed

+92
-18
lines changed

1 file changed

+92
-18
lines changed

behaviours/behaviour.js

+92-18
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
//
2424
// In this version of the library, behaviours are enabled on a per-document basis.
2525
// Call `enableBehaviours(document)` to enable behaviour handling for that document.
26+
// 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
28+
// prefer behaviours not to be enabled for the main document.
2629
//
27-
// You can get a behaviour from an element if you know the type of the behavior:
30+
// You can get a behaviour from an element if you know the type (or base type) of the behavior:
2831
//
2932
// const behaviour = getBehaviour(element, MyBehaviour);
3033
//
@@ -115,7 +118,17 @@ function addMicrotaskProperty(cls, methodName) {
115118
});
116119
}
117120

118-
/** @typedef { string } AttributeName */
121+
/**
122+
* Attribute names allow us to know when a behaviour should apply to an element.
123+
* @typedef { string } AttributeName
124+
* */
125+
126+
/**
127+
* A string that cannot be an attribute name (it ends with an equals sign).
128+
* They're used to say that a behaviour will always be available on a relevant element.
129+
* There's still a name here so type inheritance & overriding works appropriately, but the name doesn't have to be used as an attribute.
130+
* @typedef { `${string}=` } NotAttributeName
131+
* */
119132

120133
/**
121134
* Base class for attribute behaviours
@@ -147,6 +160,26 @@ export class Behaviour {
147160
disconnected() { }
148161
}
149162

163+
/**
164+
* Behaviour keys that do not require a matching element to have any attributes
165+
* @typedef { NotAttributeName | Symbol } UniversalBehaviourKey
166+
* */
167+
168+
/**
169+
* A behaviour key is used to associate a behaviour with an element
170+
* @typedef { AttributeName | UniversalBehaviourKey } BehaviourKey
171+
* */
172+
173+
/** @typedef { Record<BehaviourKey, typeof Behaviour> } BehaviourRecord */
174+
175+
/**
176+
* @param { BehaviourKey } key
177+
* @returns { key is AttributeName }
178+
*/
179+
function isAttributeName(key) {
180+
return !((typeof key === "symbol") || key.endsWith("="));
181+
}
182+
150183
const INSTANCES = Symbol("behaviour-instances");
151184

152185
/**
@@ -185,6 +218,21 @@ function* allElements(elements) {
185218
}
186219
}
187220

221+
/**
222+
* A list of all entries including Symbol-keyed ones
223+
* @param { object } o
224+
* @returns { Iterable<[string | Symbol, any]> }
225+
*/
226+
function* allEntries(o) {
227+
for (const e of Object.entries(o)) {
228+
yield e;
229+
}
230+
231+
for (const s of Object.getOwnPropertySymbols(o)) {
232+
yield [s, o[s]];
233+
}
234+
}
235+
188236
/**
189237
* Connects a behaviour of the specified type to an element
190238
* if the element doesn't already have a behaviour of that exact type.
@@ -241,13 +289,15 @@ export function getBehaviour(element, behaviourType) {
241289
return undefined;
242290
}
243291

292+
/** @typedef { { key: BehaviourKey, elementType: typeof HTMLElement, behaviourType: typeof Behaviour, existingBehaviourType: typeof Behaviour } } Warning */
293+
244294
class BehaviourRegistry {
245295

246296
/**
247297
* This contains the behaviours directly applied to a particular element type.
248298
* To get the effective behaviours for a particular element type, we need to
249299
* traverse the class hierarchy (prototype chain) which is handled by `getBehaviourRecord`
250-
* @type Map<typeof HTMLElement, Record<AttributeName, typeof Behaviour> >
300+
* @type Map<typeof HTMLElement, BehaviourRecord >
251301
* */
252302
elementTypeToBehaviourRecord = new Map();
253303

@@ -328,7 +378,7 @@ class BehaviourRegistry {
328378
* Returns the effective record of behaviours/attributes that have been
329379
* applied to the specified element type and any base classes.
330380
* @param { typeof HTMLElement } elementType
331-
* @returns { Record<AttributeName, typeof Behaviour> }
381+
* @returns { BehaviourRecord }
332382
*/
333383
getBehaviourRecord(elementType) {
334384
const elementTypeToBehaviourRecord = this.elementTypeToBehaviourRecord;
@@ -348,23 +398,25 @@ class BehaviourRegistry {
348398
* or they may indicate that you have a conflict with two behaviours both trying to use the same attribute names.
349399
* @param { typeof Behaviour } behaviourType
350400
* @param { typeof HTMLElement } elementType
351-
* @param { string[] } attributes
401+
* @param { BehaviourKey[] } keys An array of attribute names or non-attribute strings or Symbols
352402
*/
353-
register(behaviourType, elementType, attributes) {
354-
if (!Array.isArray(attributes)) {
403+
register(behaviourType, elementType, keys) {
404+
if (!Array.isArray(keys)) {
355405
throw "attributes should be an array";
356406
}
357407

408+
/** @type Warning[] */
358409
const warnings = [];
359410
const behaviours = this.getBehaviourRecord(elementType);
360-
for (const attribute of attributes) {
361-
const existingBehaviourType = behaviours[attribute];
411+
for (const key of keys) {
412+
/** @type { (typeof Behaviour) | undefined } */
413+
const existingBehaviourType = behaviours[key];
362414
if (existingBehaviourType !== undefined) {
363-
warnings.push({ attribute, elementType, behaviourType, existingBehaviourType });
415+
warnings.push({ key, elementType, behaviourType, existingBehaviourType });
364416
}
365417

366418
const localBehaviours = this.getOrCreateBehaviourRecord(elementType);
367-
localBehaviours[attribute] = behaviourType;
419+
localBehaviours[key] = behaviourType;
368420
}
369421

370422
// For the first registration, we add the main document to the queue so that users cannot forget to call enable on it
@@ -390,11 +442,10 @@ class BehaviourRegistry {
390442
*/
391443
connect(elements) {
392444
for (const element of elements) {
393-
const behaviours = getBehaviourRecord(element.constructor);
394-
if (behaviours !== undefined) {
395-
const entries = Object.entries(behaviours);
396-
for (const [attribute, behaviour] of entries) {
397-
if (element.hasAttribute(attribute)) {
445+
const behaviourRecord = this.getBehaviourRecord(element.constructor);
446+
if (behaviourRecord !== undefined) {
447+
for (const [key, behaviour] of allEntries(behaviourRecord)) {
448+
if (!isAttributeName(key) || element.hasAttribute(key)) {
398449
connect(element, behaviour);
399450
}
400451
}
@@ -477,18 +528,41 @@ addMicrotaskProperty(BehaviourRegistry, "update");
477528

478529
const customBehaviours = new BehaviourRegistry();
479530

531+
/**
532+
* Starts watching the document and connecting behaviours to relevant elements
533+
* @param { Document } document
534+
*/
480535
export function enableBehaviours(document) {
481536
customBehaviours.enable(document);
482537
}
483538

539+
/**
540+
* Stops watching the document. Does not disconnect already connected behaviours.
541+
* @param { Document } document
542+
*/
484543
export function disableBehaviours(document) {
485544
customBehaviours.disable(document);
486545
}
487546

547+
/**
548+
* Returns the effective record of behaviours/attributes that have been
549+
* applied to the specified element type and any base classes.
550+
* @param { typeof HTMLElement } elementType
551+
* @returns { BehaviourRecord }
552+
*/
488553
export function getBehaviourRecord(elementType) {
489554
return customBehaviours.getBehaviourRecord(elementType);
490555
}
491556

492-
export function registerBehaviour(behaviourType, elementType, attributes) {
493-
return customBehaviours.register(behaviourType, elementType, attributes);
557+
/**
558+
* Registers a behaviour and a set of attributes as being usable with a particular element type (and its derived classes).
559+
* Returns a list of warnings if there are existing behaviours using the same attribute names anywhere in the element class hierarchy.
560+
* The warnings may be completely benign (if you want to provide a different behaviour for an attribute in a derived class, for example)
561+
* or they may indicate that you have a conflict with two behaviours both trying to use the same attribute names.
562+
* @param { typeof Behaviour } behaviourType
563+
* @param { typeof HTMLElement } elementType
564+
* @param { BehaviourKey[] } keys An array of attribute names or non-attribute strings or Symbols
565+
*/
566+
export function registerBehaviour(behaviourType, elementType, keys) {
567+
return customBehaviours.register(behaviourType, elementType, keys);
494568
}

0 commit comments

Comments
 (0)