mount-observer 0.1.3 → 0.1.5

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/emitEvents.js CHANGED
@@ -2,15 +2,15 @@
2
2
  * Emits events from a mounted element based on the mountedElemEmits configuration.
3
3
  * This module is dynamically loaded only when mountedElemEmits is configured.
4
4
  */
5
- export async function emitMountedElementEvents(element, mountInit, processedEventsForElement) {
6
- const configs = Array.isArray(mountInit.mountedElemEmits)
7
- ? mountInit.mountedElemEmits
8
- : [mountInit.mountedElemEmits];
5
+ export async function emitMountedElementEvents(element, MountConfig, processedEventsForElement) {
6
+ const configs = Array.isArray(MountConfig.mountedElemEmits)
7
+ ? MountConfig.mountedElemEmits
8
+ : [MountConfig.mountedElemEmits];
9
9
  for (const config of configs) {
10
- await emitSingleEvent(element, mountInit, config, processedEventsForElement);
10
+ await emitSingleEvent(element, MountConfig, config, processedEventsForElement);
11
11
  }
12
12
  }
13
- async function emitSingleEvent(element, mountInit, config, processedEventsForElement) {
13
+ async function emitSingleEvent(element, MountConfig, config, processedEventsForElement) {
14
14
  // Check if this event should only fire once per element
15
15
  if (config.oncePerMountedElement) {
16
16
  const eventId = getEventId(config);
@@ -28,7 +28,7 @@ async function emitSingleEvent(element, mountInit, config, processedEventsForEle
28
28
  const EventCtor = resolveEventConstructor(config.event);
29
29
  // Process args with magic string substitution
30
30
  const processedArgs = config.args !== undefined
31
- ? processMagicStrings(config.args, element, mountInit)
31
+ ? processMagicStrings(config.args, element, MountConfig)
32
32
  : undefined;
33
33
  // Construct the event
34
34
  let event;
@@ -58,7 +58,7 @@ async function emitSingleEvent(element, mountInit, config, processedEventsForEle
58
58
  // Apply eventProps if specified
59
59
  if (config.eventProps) {
60
60
  const { assignGingerly } = await import('assign-gingerly/assignGingerly.js');
61
- const processedProps = processMagicStrings(config.eventProps, element, mountInit);
61
+ const processedProps = processMagicStrings(config.eventProps, element, MountConfig);
62
62
  assignGingerly(event, processedProps);
63
63
  }
64
64
  // Dispatch the event from the mounted element
@@ -79,23 +79,23 @@ function getEventId(config) {
79
79
  const argsStr = JSON.stringify(config.args || '');
80
80
  return `${eventName}:${argsStr}`;
81
81
  }
82
- function processMagicStrings(value, element, mountInit) {
82
+ function processMagicStrings(value, element, MountConfig) {
83
83
  if (typeof value === 'string') {
84
84
  if (value === '{{mountedElement}}') {
85
85
  return element;
86
86
  }
87
- if (value === '{{mountInit}}') {
88
- return mountInit;
87
+ if (value === '{{MountConfig}}') {
88
+ return MountConfig;
89
89
  }
90
90
  return value;
91
91
  }
92
92
  if (Array.isArray(value)) {
93
- return value.map(item => processMagicStrings(item, element, mountInit));
93
+ return value.map(item => processMagicStrings(item, element, MountConfig));
94
94
  }
95
95
  if (value && typeof value === 'object') {
96
96
  const processed = {};
97
97
  for (const [key, val] of Object.entries(value)) {
98
- processed[key] = processMagicStrings(val, element, mountInit);
98
+ processed[key] = processMagicStrings(val, element, MountConfig);
99
99
  }
100
100
  return processed;
101
101
  }
package/emitEvents.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EventConfig, EventConstructor, MountInit } from './types.d.ts';
1
+ import type { EventConfig, EventConstructor, MountConfig } from './types.d.ts';
2
2
 
3
3
  /**
4
4
  * Emits events from a mounted element based on the mountedElemEmits configuration.
@@ -6,21 +6,21 @@ import type { EventConfig, EventConstructor, MountInit } from './types.d.ts';
6
6
  */
7
7
  export async function emitMountedElementEvents(
8
8
  element: Element,
9
- mountInit: MountInit,
9
+ MountConfig: MountConfig,
10
10
  processedEventsForElement: WeakMap<Element, Set<string>>
11
11
  ): Promise<void> {
12
- const configs = Array.isArray(mountInit.mountedElemEmits)
13
- ? mountInit.mountedElemEmits
14
- : [mountInit.mountedElemEmits!];
12
+ const configs = Array.isArray(MountConfig.mountedElemEmits)
13
+ ? MountConfig.mountedElemEmits
14
+ : [MountConfig.mountedElemEmits!];
15
15
 
16
16
  for (const config of configs) {
17
- await emitSingleEvent(element, mountInit, config, processedEventsForElement);
17
+ await emitSingleEvent(element, MountConfig, config, processedEventsForElement);
18
18
  }
19
19
  }
20
20
 
21
21
  async function emitSingleEvent(
22
22
  element: Element,
23
- mountInit: MountInit,
23
+ MountConfig: MountConfig,
24
24
  config: EventConfig,
25
25
  processedEventsForElement: WeakMap<Element, Set<string>>
26
26
  ): Promise<void> {
@@ -46,7 +46,7 @@ async function emitSingleEvent(
46
46
 
47
47
  // Process args with magic string substitution
48
48
  const processedArgs = config.args !== undefined
49
- ? processMagicStrings(config.args, element, mountInit)
49
+ ? processMagicStrings(config.args, element, MountConfig)
50
50
  : undefined;
51
51
 
52
52
  // Construct the event
@@ -74,7 +74,7 @@ async function emitSingleEvent(
74
74
  // Apply eventProps if specified
75
75
  if (config.eventProps) {
76
76
  const { assignGingerly } = await import('assign-gingerly/assignGingerly.js');
77
- const processedProps = processMagicStrings(config.eventProps, element, mountInit);
77
+ const processedProps = processMagicStrings(config.eventProps, element, MountConfig);
78
78
  assignGingerly(event, processedProps);
79
79
  }
80
80
 
@@ -99,25 +99,25 @@ function getEventId(config: EventConfig): string {
99
99
  return `${eventName}:${argsStr}`;
100
100
  }
101
101
 
102
- function processMagicStrings(value: any, element: Element, mountInit: MountInit): any {
102
+ function processMagicStrings(value: any, element: Element, MountConfig: MountConfig): any {
103
103
  if (typeof value === 'string') {
104
104
  if (value === '{{mountedElement}}') {
105
105
  return element;
106
106
  }
107
- if (value === '{{mountInit}}') {
108
- return mountInit;
107
+ if (value === '{{MountConfig}}') {
108
+ return MountConfig;
109
109
  }
110
110
  return value;
111
111
  }
112
112
 
113
113
  if (Array.isArray(value)) {
114
- return value.map(item => processMagicStrings(item, element, mountInit));
114
+ return value.map(item => processMagicStrings(item, element, MountConfig));
115
115
  }
116
116
 
117
117
  if (value && typeof value === 'object') {
118
118
  const processed: any = {};
119
119
  for (const [key, val] of Object.entries(value)) {
120
- processed[key] = processMagicStrings(val, element, mountInit);
120
+ processed[key] = processMagicStrings(val, element, MountConfig);
121
121
  }
122
122
  return processed;
123
123
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Finds the highest scoped container that has the same customElementRegistry as the given element.
3
+ * This is useful for scoped custom element registries where we want to observe within the correct scope.
4
+ *
5
+ * @param element - The element to find the root registry container for
6
+ * @returns The root node or highest parent element with the same customElementRegistry
7
+ */
8
+ export function getRootRegistryContainer(element) {
9
+ const rn = element.getRootNode();
10
+ const { customElementRegistry } = element;
11
+ // If root node has the same registry, return it
12
+ if (rn.customElementRegistry === customElementRegistry) {
13
+ return rn;
14
+ }
15
+ // Walk up the parent chain to find the highest element with the same registry
16
+ let parent = element.parentElement;
17
+ while (parent) {
18
+ const prevParent = parent;
19
+ parent = parent.parentElement;
20
+ // If parent has a different registry, return the previous parent
21
+ if (parent && parent.customElementRegistry !== customElementRegistry) {
22
+ return prevParent;
23
+ }
24
+ }
25
+ // If we reached the top without finding a different registry, return the element itself
26
+ return element;
27
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Finds the highest scoped container that has the same customElementRegistry as the given element.
3
+ * This is useful for scoped custom element registries where we want to observe within the correct scope.
4
+ *
5
+ * @param element - The element to find the root registry container for
6
+ * @returns The root node or highest parent element with the same customElementRegistry
7
+ */
8
+ export function getRootRegistryContainer(element: Element): Node {
9
+ const rn = element.getRootNode();
10
+ const { customElementRegistry } = element as any;
11
+
12
+ // If root node has the same registry, return it
13
+ if ((rn as any).customElementRegistry === customElementRegistry) {
14
+ return rn;
15
+ }
16
+
17
+ // Walk up the parent chain to find the highest element with the same registry
18
+ let parent = element.parentElement;
19
+
20
+ while (parent) {
21
+ const prevParent = parent;
22
+ parent = parent.parentElement;
23
+
24
+ // If parent has a different registry, return the previous parent
25
+ if (parent && (parent as any).customElementRegistry !== customElementRegistry) {
26
+ return prevParent;
27
+ }
28
+ }
29
+
30
+ // If we reached the top without finding a different registry, return the element itself
31
+ return element;
32
+ }
package/index.js CHANGED
@@ -1,12 +1,11 @@
1
1
  // Main entry point for MountObserver v2
2
2
  export { MountObserver } from './MountObserver.js';
3
- export { whereOutside } from './whereOutside.js';
3
+ export { withScopePerimeter } from './withScopePerimeter.js';
4
4
  export { emitMountedElementEvents } from './emitEvents.js';
5
- export { checkAttrChanges } from './attrChanges.js';
6
5
  export { arr } from './arr.js';
7
6
  export { EvtRt } from './EvtRt.js';
8
7
  export { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
9
- export { mountEventName, dismountEventName, disconnectEventName, loadEventName, attrchangeEventName, mediamatchEventName, mediaunmatchEventName } from './Events.js';
8
+ export { mountEventName, dismountEventName, disconnectEventName, loadEventName, mediamatchEventName, mediaunmatchEventName } from './Events.js';
10
9
  // Register built-in handlers
11
10
  import { MountObserver } from './MountObserver.js';
12
11
  import { EvtRt } from './EvtRt.js';
package/index.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  // Main entry point for MountObserver v2
2
2
  export { MountObserver } from './MountObserver.js';
3
- export { whereOutside } from './whereOutside.js';
3
+ export { withScopePerimeter } from './withScopePerimeter.js';
4
4
  export { emitMountedElementEvents } from './emitEvents.js';
5
- export { checkAttrChanges } from './attrChanges.js';
6
5
  export { arr } from './arr.js';
7
6
  export { EvtRt } from './EvtRt.js';
8
7
  export { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
9
8
  export type {
10
- MountInit,
9
+ MountConfig,
11
10
  MountObserverOptions,
12
11
  IMountObserver,
13
12
  MountContext,
@@ -21,7 +20,6 @@ export {
21
20
  dismountEventName,
22
21
  disconnectEventName,
23
22
  loadEventName,
24
- attrchangeEventName,
25
23
  mediamatchEventName,
26
24
  mediaunmatchEventName
27
25
  } from './Events.js';
@@ -29,7 +27,8 @@ export {
29
27
  // Register built-in handlers
30
28
  import { MountObserver } from './MountObserver.js';
31
29
  import { EvtRt } from './EvtRt.js';
32
- import { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
30
+ import { DefineCustomElementHandler, DefineScopedCustomElementHandler } from './DefineCustomElementHandler.js';
33
31
 
34
32
  MountObserver.define('builtIns.logToConsole', EvtRt);
35
33
  MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
34
+ MountObserver.define('buildIns.defineScopedCustomElement', DefineScopedCustomElementHandler);
package/loadImports.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Dynamic import loading utilities
2
- // Only loaded when MountInit.import is specified
2
+ // Only loaded when MountConfig.import is specified
3
3
  import { arr } from './arr.js';
4
4
  export async function loadImports(imports) {
5
5
  const importArray = arr(imports);
package/loadImports.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Dynamic import loading utilities
2
- // Only loaded when MountInit.import is specified
2
+ // Only loaded when MountConfig.import is specified
3
3
 
4
4
  import { ImportSpec } from './types.js';
5
5
  import { arr } from './arr.js';
package/mediaQuery.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { MediaMatchEvent, MediaUnmatchEvent, DismountEvent } from './Events.js';
2
2
  export function setupMediaQuery(init, rootNodeRef, mountedElements, modules, observer, processNode) {
3
- const { whereMediaMatches } = init;
3
+ const { withMediaMatching } = init;
4
4
  // Create or use MediaQueryList
5
5
  let mediaQueryList;
6
- if (typeof whereMediaMatches === 'string') {
7
- mediaQueryList = window.matchMedia(whereMediaMatches);
6
+ if (typeof withMediaMatching === 'string') {
7
+ mediaQueryList = window.matchMedia(withMediaMatching);
8
8
  }
9
9
  else {
10
- mediaQueryList = whereMediaMatches;
10
+ mediaQueryList = withMediaMatching;
11
11
  }
12
12
  // Track current state
13
13
  let mediaMatches = mediaQueryList.matches;
@@ -49,7 +49,7 @@ export function setupMediaQuery(init, rootNodeRef, mountedElements, modules, obs
49
49
  modules,
50
50
  observer: observer,
51
51
  rootNode,
52
- mountInit: init
52
+ MountConfig: init
53
53
  };
54
54
  // Get all mounted elements from the WeakDual setWeak
55
55
  const mountedElementsList = [];
package/mediaQuery.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // Media query handling for MountObserver
2
- import type { MountInit, MountContext, WeakDual } from './types.js';
2
+ import type { MountConfig, MountContext, WeakDual } from './types.js';
3
3
  import { MediaMatchEvent, MediaUnmatchEvent, DismountEvent } from './Events.js';
4
4
 
5
5
  export function setupMediaQuery(
6
- init: MountInit,
6
+ init: MountConfig,
7
7
  rootNodeRef: WeakRef<Node>,
8
8
  mountedElements: WeakDual<Element>,
9
9
  modules: any[],
@@ -14,14 +14,14 @@ export function setupMediaQuery(
14
14
  mediaMatches: boolean;
15
15
  cleanup: () => void;
16
16
  } {
17
- const { whereMediaMatches } = init;
17
+ const { withMediaMatching } = init;
18
18
 
19
19
  // Create or use MediaQueryList
20
20
  let mediaQueryList: MediaQueryList;
21
- if (typeof whereMediaMatches === 'string') {
22
- mediaQueryList = window.matchMedia(whereMediaMatches);
21
+ if (typeof withMediaMatching === 'string') {
22
+ mediaQueryList = window.matchMedia(withMediaMatching);
23
23
  } else {
24
- mediaQueryList = whereMediaMatches!;
24
+ mediaQueryList = withMediaMatching!;
25
25
  }
26
26
 
27
27
  // Track current state
@@ -70,7 +70,7 @@ export function setupMediaQuery(
70
70
  modules,
71
71
  observer: observer as any,
72
72
  rootNode,
73
- mountInit: init
73
+ MountConfig: init
74
74
  };
75
75
 
76
76
  // Get all mounted elements from the WeakDual setWeak
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
7
7
  "dependencies": {
8
- "assign-gingerly": "0.0.6"
8
+ "assign-gingerly": "0.0.12"
9
9
  },
10
10
  "devDependencies": {
11
11
  "@playwright/test": "1.58.2",
@@ -20,18 +20,14 @@
20
20
  "default": "./MountObserver.js",
21
21
  "types": "./MountObserver.ts"
22
22
  },
23
- "./whereOutside.js": {
24
- "default": "./whereOutside.js",
25
- "types": "./whereOutside.ts"
23
+ "./withScopePerimeter.js": {
24
+ "default": "./withScopePerimeter.js",
25
+ "types": "./withScopePerimeter.ts"
26
26
  },
27
27
  "./emitEvents.js": {
28
28
  "default": "./emitEvents.js",
29
29
  "types": "./emitEvents.ts"
30
30
  },
31
- "./attrChanges.js": {
32
- "default": "./attrChanges.js",
33
- "types": "./attrChanges.ts"
34
- },
35
31
  "./arr.js": {
36
32
  "default": "./arr.js",
37
33
  "types": "./arr.ts"
@@ -15,14 +15,14 @@ const config: PlaywrightTestConfig = {
15
15
  name: 'chromium',
16
16
  use: { ...devices['Desktop Chrome'] },
17
17
  },
18
- {
19
- name: 'firefox',
20
- use: { ...devices['Desktop Firefox'] },
21
- },
22
- {
23
- name: 'webkit',
24
- use: { ...devices['Desktop Safari'] },
25
- },
18
+ // {
19
+ // name: 'firefox',
20
+ // use: { ...devices['Desktop Firefox'] },
21
+ // },
22
+ // {
23
+ // name: 'webkit',
24
+ // use: { ...devices['Desktop Safari'] },
25
+ // },
26
26
  ],
27
27
  };
28
28
  export default config;
package/types.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  // Core types for MountObserver v2 - Polyfill Supported Scenario I
2
2
 
3
+ import {EnhancementConfig} from 'assign-gingerly/types.d.ts';
4
+
3
5
  export type Constructor = new (...args: any[]) => any;
4
6
 
5
7
  export type EventConstructor = {new(...args: any[]): Event};
@@ -13,51 +15,29 @@ export interface EventConfig {
13
15
 
14
16
  export type DismountReason =
15
17
  | 'media-query-failed'
16
- | 'where-element-matches-failed';
17
-
18
- export interface MountInit {
19
- whereElementMatches: string;
20
- whereAttr?: WhereAttr;
21
- whereInstanceOf?: Constructor | Constructor[];
22
- whereMediaMatches?: string | MediaQueryList;
23
- whereOutside?: string;
18
+ | 'with-matching-failed';
19
+
20
+ export interface MountConfig {
21
+ matching?: string;
22
+ withInstance?: Constructor | Constructor[];
23
+ withMediaMatching?: string | MediaQueryList;
24
+ withScopePerimeter?: string;
24
25
  import?: string | ImportSpec | Array<string | ImportSpec>;
25
26
  do?: string | DoCallback | (string | DoCallback)[];
26
27
  loadingEagerness?: 'eager' | 'lazy';
27
28
  assignOnMount?: Record<string, any>;
28
29
  assignOnDismount?: Record<string, any>;
29
- map?: MapConfig;
30
+ stageOnMount?: Record<string, any>;
30
31
  getPlayByPlay?: boolean;
31
32
  mountedElemEmits?: EventConfig | EventConfig[];
32
33
  reference?: number | number[];
33
34
  //allow handler classes or functions
34
35
  //to be passed some custom information
35
36
  customData?: unknown;
37
+ enhancementConfig?: EnhancementConfig | EnhancementConfig[];
36
38
  }
37
39
 
38
- export interface MapConfig {
39
- [coordinate: string]: MapEntry;
40
- }
41
-
42
- export interface MapEntry {
43
- instanceOf?: string;
44
- mapsTo?: string;
45
- /**
46
- * Only notify the presence of this attribute
47
- * the first time it is seen
48
- */
49
- once?: boolean;
50
- [key: string]: any;
51
- }
52
-
53
- export type BranchValue = string | { [key: string]: BranchValue[] };
54
40
 
55
- export interface WhereAttr {
56
- hasBuiltInRootIn?: string[];
57
- hasCERootIn?: string[];
58
- hasBase: string;
59
- hasBranchIn?: BranchValue[];
60
- }
61
41
 
62
42
  export interface ImportSpec {
63
43
  url: string;
@@ -68,7 +48,7 @@ export interface MountContext {
68
48
  modules: any[];
69
49
  observer: IMountObserver;
70
50
  rootNode: Node;
71
- mountInit: MountInit
51
+ MountConfig: MountConfig
72
52
  }
73
53
 
74
54
 
@@ -82,8 +62,16 @@ export type DoCallback = (mountedElement: Element, context: MountContext) => voi
82
62
  // reconnect?: (mountedElement: Element, context: MountContext) => void;
83
63
  // }
84
64
 
65
+ export type MountScope =
66
+ | 'registry' // getRootRegistryContainer (default)
67
+ | 'self' // this element
68
+ | 'root' // getRootNode()
69
+ | 'shadow' // shadowRoot (throws if none)
70
+ | Element; // custom element to observe
71
+
85
72
  export interface MountObserverOptions {
86
73
  disconnectedSignal?: AbortSignal;
74
+ scope?: MountScope;
87
75
  }
88
76
 
89
77
  export interface WeakDual<T extends Object>{
@@ -102,26 +90,14 @@ export interface IMountObserver extends EventTarget {
102
90
  export interface IMountEvent extends Event {
103
91
  mountedElement: Element;
104
92
  modules: any[];
105
- mountInit: MountInit;
93
+ MountConfig: MountConfig;
106
94
  mountContext: MountContext;
107
95
  }
108
96
 
109
97
  export interface IDismountEvent extends Event {
110
98
  mountedElement: Element;
111
99
  reason: DismountReason;
112
- mountInit: MountInit;
100
+ MountConfig: MountConfig;
113
101
  }
114
102
 
115
- export interface IAttrChangeEvent extends Event {
116
- changes: AttrChange[];
117
- mountInit: MountInit;
118
- }
119
103
 
120
- export interface AttrChange {
121
- value: string | null;
122
- attrNode: Attr | null;
123
- mapEntry: MapEntry | null;
124
- attrName: string;
125
- coordinate: string;
126
- element: Element;
127
- }
@@ -7,7 +7,7 @@
7
7
  * @param outside - CSS selector for excluding ancestors
8
8
  * @returns true if element is outside all matching ancestors, false otherwise
9
9
  */
10
- export function whereOutside(rootNode, matchCandidate, outside) {
10
+ export function withScopePerimeter(rootNode, matchCandidate, outside) {
11
11
  let current = matchCandidate.parentElement;
12
12
  while (current && current !== rootNode) {
13
13
  if (current.matches(outside)) {
@@ -7,7 +7,7 @@
7
7
  * @param outside - CSS selector for excluding ancestors
8
8
  * @returns true if element is outside all matching ancestors, false otherwise
9
9
  */
10
- export function whereOutside(
10
+ export function withScopePerimeter(
11
11
  rootNode: Node,
12
12
  matchCandidate: Element,
13
13
  outside: string
package/attrChanges.js DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * Checks for attribute changes on a mounted element.
3
- * This module is dynamically loaded only when whereAttr is configured.
4
- */
5
- export function checkAttrChanges(element, mountInit, buildAttrCoordinateMapFn, elementAttrStates, elementOnceAttrs) {
6
- if (!mountInit.whereAttr || !buildAttrCoordinateMapFn) {
7
- return [];
8
- }
9
- const isCustomElement = element.tagName.toLowerCase().includes('-');
10
- const attrCoordMap = buildAttrCoordinateMapFn(mountInit.whereAttr, isCustomElement);
11
- // Get or create the attribute state for this element
12
- let attrState = elementAttrStates.get(element);
13
- if (!attrState) {
14
- attrState = new Map();
15
- elementAttrStates.set(element, attrState);
16
- }
17
- const changes = [];
18
- const currentAttrs = new Set();
19
- // Check all possible attributes from the coordinate map
20
- for (const attrName of Object.keys(attrCoordMap)) {
21
- const coordinate = attrCoordMap[attrName];
22
- const currentValue = element.getAttribute(attrName);
23
- const previousValue = attrState.get(attrName);
24
- if (currentValue !== null) {
25
- currentAttrs.add(attrName);
26
- }
27
- // Check if this attribute has "once: true" in its map entry
28
- const mapEntry = mountInit.map?.[coordinate] || null;
29
- const isOnce = mapEntry?.once === true;
30
- // If "once" is true, check if we've already seen this attribute
31
- if (isOnce) {
32
- let onceAttrs = elementOnceAttrs.get(element);
33
- if (!onceAttrs) {
34
- onceAttrs = new Set();
35
- elementOnceAttrs.set(element, onceAttrs);
36
- }
37
- // If we've already seen this attribute, skip it
38
- if (onceAttrs.has(attrName)) {
39
- continue;
40
- }
41
- // Mark this attribute as seen if it currently has a value
42
- if (currentValue !== null) {
43
- onceAttrs.add(attrName);
44
- }
45
- }
46
- // Include if: currently has value OR previously had value but now removed
47
- if (currentValue !== null || (previousValue !== undefined && currentValue === null)) {
48
- // Check if value changed
49
- if (currentValue !== previousValue) {
50
- const attrNode = currentValue !== null ? element.getAttributeNode(attrName) : null;
51
- changes.push({
52
- value: currentValue,
53
- attrNode,
54
- mapEntry,
55
- attrName,
56
- coordinate,
57
- element
58
- });
59
- // Update state
60
- if (currentValue !== null) {
61
- attrState.set(attrName, currentValue);
62
- }
63
- else {
64
- attrState.delete(attrName);
65
- }
66
- }
67
- }
68
- }
69
- return changes;
70
- }