mount-observer 0.0.4 → 0.0.6

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/MountObserver.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { RootMutObs } from './RootMutObs.js';
2
2
  const mutationObserverLookup = new WeakMap();
3
+ const refCount = new WeakMap();
3
4
  export class MountObserver extends EventTarget {
4
5
  #mountInit;
5
6
  #rootMutObs;
@@ -44,17 +45,52 @@ export class MountObserver extends EventTarget {
44
45
  this.#calculatedSelector = matches.join(',');
45
46
  return this.#calculatedSelector;
46
47
  }
48
+ unobserve(within) {
49
+ const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
50
+ const currentCount = refCount.get(nodeToMonitor);
51
+ if (currentCount !== undefined) {
52
+ if (currentCount <= 1) {
53
+ const observer = mutationObserverLookup.get(nodeToMonitor);
54
+ if (observer === undefined) {
55
+ console.warn(refCountErr);
56
+ }
57
+ else {
58
+ observer.disconnect();
59
+ mutationObserverLookup.delete(nodeToMonitor);
60
+ refCount.delete(nodeToMonitor);
61
+ }
62
+ }
63
+ else {
64
+ refCount.set(nodeToMonitor, currentCount + 1);
65
+ }
66
+ }
67
+ else {
68
+ if (mutationObserverLookup.has(nodeToMonitor)) {
69
+ console.warn(refCountErr);
70
+ }
71
+ }
72
+ }
47
73
  async observe(within) {
48
74
  const nodeToMonitor = this.#isComplex ? (within instanceof ShadowRoot ? within : within.getRootNode()) : within;
49
75
  if (!mutationObserverLookup.has(nodeToMonitor)) {
50
76
  mutationObserverLookup.set(nodeToMonitor, new RootMutObs(nodeToMonitor));
77
+ refCount.set(nodeToMonitor, 1);
78
+ }
79
+ else {
80
+ const currentCount = refCount.get(nodeToMonitor);
81
+ if (currentCount === undefined) {
82
+ console.warn(refCountErr);
83
+ }
84
+ else {
85
+ refCount.set(nodeToMonitor, currentCount + 1);
86
+ }
51
87
  }
52
88
  const rootMutObs = mutationObserverLookup.get(within);
53
89
  const { attribMatches } = this.#mountInit;
54
90
  rootMutObs.addEventListener('mutation-event', (e) => {
55
91
  //TODO: disconnected
56
92
  if (this.#isComplex) {
57
- this.#inspectWithin(within);
93
+ this.#inspectWithin(within, false);
58
94
  return;
59
95
  }
60
96
  const { mutationRecords } = e;
@@ -109,9 +145,11 @@ export class MountObserver extends EventTarget {
109
145
  this.dispatchEvent(new DisconnectEvent(deletedElement));
110
146
  }
111
147
  }
112
- this.#filterAndMount(elsToInspect, true);
148
+ this.#filterAndMount(elsToInspect, true, false);
113
149
  }, { signal: this.#abortController.signal });
114
- await this.#inspectWithin(within);
150
+ //if(ignoreInitialMatches !== true){
151
+ await this.#inspectWithin(within, true);
152
+ //}
115
153
  }
116
154
  #confirmInstanceOf(el, whereInstanceOf) {
117
155
  for (const test of whereInstanceOf) {
@@ -120,7 +158,7 @@ export class MountObserver extends EventTarget {
120
158
  }
121
159
  return false;
122
160
  }
123
- async #mount(matching) {
161
+ async #mount(matching, initializing) {
124
162
  //first unmount non matching
125
163
  const alreadyMounted = this.#filterAndDismount();
126
164
  const onMount = this.#mountInit.do?.onMount;
@@ -141,13 +179,20 @@ export class MountObserver extends EventTarget {
141
179
  }
142
180
  break;
143
181
  case 'function':
144
- this.module = await imp(match, this, 'Import');
182
+ this.module = await imp(match, this, {
183
+ stage: 'Import',
184
+ initializing
185
+ });
145
186
  break;
146
187
  }
147
188
  }
148
- if (onMount !== undefined)
149
- onMount(match, this, 'PostImport');
150
- this.dispatchEvent(new MountEvent(match));
189
+ if (onMount !== undefined) {
190
+ onMount(match, this, {
191
+ stage: 'PostImport',
192
+ initializing
193
+ });
194
+ }
195
+ this.dispatchEvent(new MountEvent(match, initializing));
151
196
  if (attribMatches !== undefined) {
152
197
  let idx = 0;
153
198
  for (const attribMatch of attribMatches) {
@@ -178,7 +223,7 @@ export class MountObserver extends EventTarget {
178
223
  const onDismount = this.#mountInit.do?.onDismount;
179
224
  for (const unmatch of unmatching) {
180
225
  if (onDismount !== undefined) {
181
- onDismount(unmatch, this);
226
+ onDismount(unmatch, this, {});
182
227
  }
183
228
  this.dispatchEvent(new DismountEvent(unmatch));
184
229
  }
@@ -195,7 +240,7 @@ export class MountObserver extends EventTarget {
195
240
  if (!x.matches(match))
196
241
  return true;
197
242
  if (whereSatisfies !== undefined) {
198
- if (!whereSatisfies(x, this, 'Inspecting'))
243
+ if (!whereSatisfies(x, this, { stage: 'Inspecting', initializing: false }))
199
244
  return true;
200
245
  }
201
246
  returnSet.add(x);
@@ -206,7 +251,7 @@ export class MountObserver extends EventTarget {
206
251
  this.#mountedList = Array.from(returnSet).map(x => new WeakRef(x));
207
252
  return returnSet;
208
253
  }
209
- async #filterAndMount(els, checkMatch) {
254
+ async #filterAndMount(els, checkMatch, initializing) {
210
255
  const { whereSatisfies, whereInstanceOf } = this.#mountInit;
211
256
  const match = this.#selector;
212
257
  const elsToMount = els.filter(x => {
@@ -215,7 +260,7 @@ export class MountObserver extends EventTarget {
215
260
  return false;
216
261
  }
217
262
  if (whereSatisfies !== undefined) {
218
- if (!whereSatisfies(x, this, 'Inspecting'))
263
+ if (!whereSatisfies(x, this, { stage: 'Inspecting', initializing }))
219
264
  return false;
220
265
  }
221
266
  if (whereInstanceOf !== undefined) {
@@ -224,16 +269,14 @@ export class MountObserver extends EventTarget {
224
269
  }
225
270
  return true;
226
271
  });
227
- this.#mount(elsToMount);
272
+ this.#mount(elsToMount, initializing);
228
273
  }
229
- async #inspectWithin(within) {
274
+ async #inspectWithin(within, initializing) {
230
275
  const els = Array.from(within.querySelectorAll(this.#selector));
231
- this.#filterAndMount(els, false);
232
- }
233
- unobserve() {
234
- throw 'NI';
276
+ this.#filterAndMount(els, false, initializing);
235
277
  }
236
278
  }
279
+ const refCountErr = 'mount-observer ref count mismatch';
237
280
  // https://github.com/webcomponents-cg/community-protocols/issues/12#issuecomment-872415080
238
281
  /**
239
282
  * The `mutation-event` event represents something that happened.
@@ -241,10 +284,12 @@ export class MountObserver extends EventTarget {
241
284
  */
242
285
  export class MountEvent extends Event {
243
286
  mountedElement;
287
+ initializing;
244
288
  static eventName = 'mount';
245
- constructor(mountedElement) {
289
+ constructor(mountedElement, initializing) {
246
290
  super(MountEvent.eventName);
247
291
  this.mountedElement = mountedElement;
292
+ this.initializing = initializing;
248
293
  }
249
294
  }
250
295
  export class DismountEvent extends Event {
package/README.md CHANGED
@@ -3,13 +3,14 @@
3
3
  [![How big is this package in your project?](https://img.shields.io/bundlephobia/minzip/mount-observer?style=for-the-badge)](https://bundlephobia.com/result?p=mount-observer)
4
4
  <img src="http://img.badgesize.io/https://cdn.jsdelivr.net/npm/mount-observer?compression=gzip">
5
5
 
6
+
6
7
  # The MountObserver api.
7
8
 
8
9
  Author: Bruce B. Anderson
9
10
 
10
11
  Issues / pr's / polyfill: [mount-observer](https://github.com/bahrus/mount-observer)
11
12
 
12
- Last Update: 2023-11-18
13
+ Last Update: 2023-11-23
13
14
 
14
15
  ## Benefits of this API
15
16
 
@@ -133,13 +134,16 @@ const observer = new MountObserver({
133
134
  },
134
135
  onDismount: ...,
135
136
  onDisconnect: ...,
137
+ onOutsideRootNode: ...,
136
138
  onReconnect: ...,
137
- onReconfirm: ...
139
+ onReconfirm: ...,
138
140
  onOutOfScope: ...,
139
141
  }
140
142
  })
141
143
  ```
142
144
 
145
+ Callbacks like we see above are useful for tight coupling, and probably are unmatched in terms of performance. However, since these rules may be of interest to multiple parties, it is usesful to also provide the ability for multiple parties to subscribe to these css rules. This can be done via:
146
+
143
147
  ## Subscribing
144
148
 
145
149
  Subscribing can be done via:
@@ -178,11 +182,11 @@ The moment a MountObserver instance's "observe" method is called (passing in a r
178
182
 
179
183
  If an element that is in "mounted" state according to a MountObserver instance is moved from one parent DOM element to another:
180
184
 
181
- 1) "disconnect" event is dispatched from the MountObserver instance the moment the element is disconnected from the DOM fragment.
185
+ 1) "disconnect" event is dispatched from the MountObserver instance the moment the mounted element is disconnected from the DOM fragment.
182
186
  2) If/when the element is added somewhere else in the DOM tree, the mountObserver instance will dispatch event "reconnect", regardless of where. [Note: can't polyfill this very easily]
183
- 3) If the element is added outside the rootNode being observed, the mountObserver instance will dispatch event "outside-root-node", and the MountObserver instance will relinquish any further responsibility for this element. Ideally this would also be dispatched when the platform garbage collects the element as well after all hard references are relinquished.
184
- 4) If the new place it was added remains within the original rootNode and remains either dismounted or mounted, the MountObserver instance dispatches event "reconfirmed".
185
- 5) If the element no longer satisfies the criteria of the MountObserver instance, the MountObserver instance will dispatch event "dismount". The same is done in reverse for moved elements that started out in a "dismounted" state.
187
+ 3) If the mounted element is added outside the rootNode being observed, the mountObserver instance will dispatch event "outside-root-node", and the MountObserver instance will relinquish any further responsibility for this element. Ideally this would also be dispatched when the platform garbage collects the element as well after all hard references are relinquished.
188
+ 4) If the new place it was added remains within the original rootNode and remains mounted, the MountObserver instance dispatches event "reconfirmed".
189
+ 5) If the element no longer satisfies the criteria of the MountObserver instance, the MountObserver instance will dispatch event "dismount".
186
190
 
187
191
  ## Special support for observable attributes
188
192
 
@@ -206,6 +210,14 @@ Example:
206
210
  });
207
211
  mo.addEventListener('attr-change', e => {
208
212
  console.log(e);
213
+ // {
214
+ // attrChangeInfo:{
215
+ // name: 'test-1',
216
+ // oldValue: null,
217
+ // newValue: 'hello'
218
+ // idx: 0,
219
+ // }
220
+ // }
209
221
  });
210
222
  mo.observe(div);
211
223
  setTimeout(() => {
package/RootMutObs.js CHANGED
@@ -12,6 +12,9 @@ export class RootMutObs extends EventTarget {
12
12
  });
13
13
  }
14
14
  #mutationObserver;
15
+ disconnect() {
16
+ this.#mutationObserver.disconnect();
17
+ }
15
18
  }
16
19
  // https://github.com/webcomponents-cg/community-protocols/issues/12#issuecomment-872415080
17
20
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
package/types.d.ts CHANGED
@@ -7,13 +7,17 @@ export interface MountInit{
7
7
  readonly whereSatisfies?: PipelineProcessor<boolean>,
8
8
  readonly import?: ImportString | [ImportString, ImportAssertions] | PipelineProcessor,
9
9
  readonly do?: {
10
- readonly onMount: PipelineProcessor,
11
- readonly onDismount: PipelineProcessor,
12
- readonly onDisconnect: PipelineProcessor,
13
- readonly onReconfirmed: PipelineProcessor,
14
- readonly onOutsideRootNode: PipelineProcessor,
10
+ readonly onMount?: PipelineProcessor,
11
+ readonly onDismount?: PipelineProcessor,
12
+ readonly onDisconnect?: PipelineProcessor,
13
+ readonly onReconfirmed?: PipelineProcessor,
14
+ readonly onOutsideRootNode?: PipelineProcessor,
15
15
  }
16
-
16
+ // /**
17
+ // * Purpose -- there are scenarios where we may only want to affect changes that occur after the initial
18
+ // * server rendering, so we only want to mount elements that appear
19
+ // */
20
+ // readonly ignoreInitialMatches?: boolean,
17
21
  }
18
22
  type CSSMatch = string;
19
23
  type ImportString = string;
@@ -27,17 +31,22 @@ export interface AttribMatch{
27
31
  // validator?: (v: any) => boolean;
28
32
  }
29
33
 
30
- export interface MountContext {
34
+ export interface IMountObserver {
31
35
  // readonly mountInit: MountInit,
32
36
  // readonly mountedRefs: WeakRef<Element>[],
33
37
  // readonly dismountedRefs: WeakRef<Element>[],
34
38
  observe(within: Node): void;
35
- unobserve(): void;
39
+ unobserve(within: Node): void;
36
40
  module?: any;
37
41
  }
38
42
 
39
- type PipelineStage = 'Inspecting' | 'PreImport' | 'PostImport' | 'Import'
40
- export type PipelineProcessor<ReturnType = void> = (matchingElement: Element, ctx: MountContext, stage?: PipelineStage) => Promise<ReturnType>;
43
+ export interface MountContext{
44
+ stage?: PipelineStage,
45
+ initializing?: boolean,
46
+ }
47
+
48
+ type PipelineStage = 'Inspecting' | 'PreImport' | 'PostImport' | 'Import'
49
+ export type PipelineProcessor<ReturnType = void> = (matchingElement: Element, observer: IMountObserver, ctx: MountContext) => Promise<ReturnType>;
41
50
 
42
51
  //#region mutation event
43
52
  export type mutationEventName = 'mutation-event';