mount-observer 0.0.111 → 0.1.0

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.
Files changed (179) hide show
  1. package/Events.js +28 -26
  2. package/Events.ts +34 -30
  3. package/MountObserver.js +235 -520
  4. package/MountObserver.ts +281 -542
  5. package/README.md +149 -56
  6. package/attrCoordinates.js +93 -0
  7. package/attrCoordinates.ts +122 -0
  8. package/constants.js +6 -0
  9. package/constants.ts +7 -0
  10. package/index.js +3 -0
  11. package/index.ts +19 -0
  12. package/loadImports.js +47 -0
  13. package/loadImports.ts +56 -0
  14. package/package.json +8 -115
  15. package/playwright.config.ts +0 -1
  16. package/types.d.ts +86 -0
  17. package/whereAttr.js +174 -0
  18. package/whereAttr.ts +221 -0
  19. package/LICENSE +0 -21
  20. package/Newish.js +0 -145
  21. package/Newish.ts +0 -169
  22. package/ObsAttr.js +0 -18
  23. package/ObsAttr.ts +0 -18
  24. package/RootMutObs.js +0 -49
  25. package/RootMutObs.ts +0 -58
  26. package/Synthesizer.js +0 -125
  27. package/Synthesizer.ts +0 -130
  28. package/bindish.js +0 -15
  29. package/bindish.ts +0 -22
  30. package/compose.js +0 -148
  31. package/compose.ts +0 -164
  32. package/doCleanup.js +0 -31
  33. package/doCleanup.ts +0 -34
  34. package/getWhereAttrSelector.js +0 -83
  35. package/getWhereAttrSelector.ts +0 -92
  36. package/preloadContent.js +0 -44
  37. package/preloadContent.ts +0 -47
  38. package/readAttrs.ts +0 -60
  39. package/refid/README.md +0 -259
  40. package/refid/arr.js +0 -4
  41. package/refid/arr.ts +0 -4
  42. package/refid/camelToKebab.js +0 -4
  43. package/refid/camelToKebab.ts +0 -4
  44. package/refid/genIds.js +0 -190
  45. package/refid/genIds.ts +0 -177
  46. package/refid/getAdjRefs.js +0 -38
  47. package/refid/getAdjRefs.ts +0 -38
  48. package/refid/getContext.js +0 -13
  49. package/refid/getContext.ts +0 -14
  50. package/refid/getCount.js +0 -8
  51. package/refid/getCount.ts +0 -8
  52. package/refid/getIsh.js +0 -35
  53. package/refid/getIsh.ts +0 -37
  54. package/refid/hostish.js +0 -18
  55. package/refid/hostish.ts +0 -20
  56. package/refid/ism.js +0 -78
  57. package/refid/ism.ts +0 -81
  58. package/refid/itemprops.js +0 -60
  59. package/refid/itemprops.ts +0 -67
  60. package/refid/joinMatching.js +0 -56
  61. package/refid/joinMatching.ts +0 -54
  62. package/refid/nudge.js +0 -23
  63. package/refid/nudge.ts +0 -23
  64. package/refid/regIsh.js +0 -27
  65. package/refid/regIsh.ts +0 -31
  66. package/refid/secretKeys.js +0 -5
  67. package/refid/secretKeys.ts +0 -5
  68. package/refid/splitRefs.js +0 -6
  69. package/refid/splitRefs.ts +0 -6
  70. package/refid/stdVal.js +0 -15
  71. package/refid/stdVal.ts +0 -15
  72. package/refid/via.js +0 -114
  73. package/refid/via.ts +0 -113
  74. package/slotkin/affine.js +0 -39
  75. package/slotkin/affine.ts +0 -46
  76. package/slotkin/beKindred.js +0 -45
  77. package/slotkin/beKindred.ts +0 -55
  78. package/slotkin/getBreadth.js +0 -19
  79. package/slotkin/getBreadth.ts +0 -21
  80. package/slotkin/getFrag.js +0 -22
  81. package/slotkin/getFrag.ts +0 -21
  82. package/slotkin/toQuery.js +0 -12
  83. package/slotkin/toQuery.ts +0 -13
  84. package/slotkin/wrap.js +0 -13
  85. package/slotkin/wrap.ts +0 -18
  86. package/ts-refs/LICENSE +0 -21
  87. package/ts-refs/README.md +0 -18
  88. package/ts-refs/be-a-beacon/types.d.ts +0 -22
  89. package/ts-refs/be-alit/types.d.ts +0 -1
  90. package/ts-refs/be-based/types.d.ts +0 -32
  91. package/ts-refs/be-bound/types.d.ts +0 -65
  92. package/ts-refs/be-buttoned-up/types.d.ts +0 -21
  93. package/ts-refs/be-calculating/types.d.ts +0 -57
  94. package/ts-refs/be-clonable/types.d.ts +0 -28
  95. package/ts-refs/be-committed/types.d.ts +0 -26
  96. package/ts-refs/be-consoling/types.d.ts +0 -25
  97. package/ts-refs/be-counted/types.d.ts +0 -88
  98. package/ts-refs/be-delible/types.d.ts +0 -26
  99. package/ts-refs/be-directive/types.d.ts +0 -43
  100. package/ts-refs/be-dispatching/types.d.ts +0 -41
  101. package/ts-refs/be-elevating/types.d.ts +0 -55
  102. package/ts-refs/be-enhanced/types.d.ts +0 -32
  103. package/ts-refs/be-enhancing/types.d.ts +0 -31
  104. package/ts-refs/be-evanescent/types.d.ts +0 -20
  105. package/ts-refs/be-eventing/types.d.ts +0 -27
  106. package/ts-refs/be-exportable/types.d.ts +0 -26
  107. package/ts-refs/be-fetching/types.d.ts +0 -73
  108. package/ts-refs/be-flashy/types.d.ts +0 -27
  109. package/ts-refs/be-formalizing/types.d.ts +0 -29
  110. package/ts-refs/be-formidable/types.d.ts +0 -64
  111. package/ts-refs/be-giddy/types.d.ts +0 -26
  112. package/ts-refs/be-gingerly/types.d.ts +0 -19
  113. package/ts-refs/be-gone/types.d.ts +0 -24
  114. package/ts-refs/be-hashing-out/types.d.ts +0 -22
  115. package/ts-refs/be-hive/types.d.ts +0 -18
  116. package/ts-refs/be-imbued/types.d.ts +0 -30
  117. package/ts-refs/be-included/types.d.ts +0 -20
  118. package/ts-refs/be-inclusive/types.d.ts +0 -30
  119. package/ts-refs/be-intersectional/types.d.ts +0 -37
  120. package/ts-refs/be-intl/types.d.ts +0 -28
  121. package/ts-refs/be-invoking/types.d.ts +0 -28
  122. package/ts-refs/be-joining/types.d.ts +0 -26
  123. package/ts-refs/be-kvetching/types.d.ts +0 -24
  124. package/ts-refs/be-lazy/types.d.ts +0 -29
  125. package/ts-refs/be-literate/types.d.ts +0 -29
  126. package/ts-refs/be-mediating/types.d.ts +0 -34
  127. package/ts-refs/be-methodical/types.d.ts +0 -20
  128. package/ts-refs/be-modding/types.d.ts +0 -18
  129. package/ts-refs/be-observant/types.d.ts +0 -27
  130. package/ts-refs/be-observing/types.d.ts +0 -84
  131. package/ts-refs/be-parsed/types.d.ts +0 -19
  132. package/ts-refs/be-parsing/types.d.ts +0 -37
  133. package/ts-refs/be-persistent/types.d.ts +0 -66
  134. package/ts-refs/be-propagating/types.d.ts +0 -26
  135. package/ts-refs/be-reformable/types.d.ts +0 -48
  136. package/ts-refs/be-render-neutral/types.d.ts +0 -31
  137. package/ts-refs/be-scoped/types.d.ts +0 -24
  138. package/ts-refs/be-sharing/types.d.ts +0 -17
  139. package/ts-refs/be-switched/types.d.ts +0 -155
  140. package/ts-refs/be-typed/types.d.ts +0 -36
  141. package/ts-refs/be-value-added/types.d.ts +0 -34
  142. package/ts-refs/be-valued/types.d.ts +0 -22
  143. package/ts-refs/be-written/types.d.ts +0 -59
  144. package/ts-refs/css-charts/types.d.ts +0 -38
  145. package/ts-refs/css-echarts/types.d.ts +0 -13
  146. package/ts-refs/data-props/types.d.ts +0 -27
  147. package/ts-refs/do-inc/types.d.ts +0 -28
  148. package/ts-refs/do-invoke/types.d.ts +0 -28
  149. package/ts-refs/do-toggle/types.d.ts +0 -27
  150. package/ts-refs/em-bower/types.d.ts +0 -18
  151. package/ts-refs/fetch-for/types.d.ts +0 -37
  152. package/ts-refs/folder-picker/types.d.ts +0 -43
  153. package/ts-refs/for-fetch/doc.d.ts +0 -98
  154. package/ts-refs/for-fetch/types.d.ts +0 -83
  155. package/ts-refs/mount-observer/types.d.ts +0 -248
  156. package/ts-refs/mt-si/types.d.ts +0 -21
  157. package/ts-refs/per-each/types.d.ts +0 -51
  158. package/ts-refs/soak-up/types.d.ts +0 -36
  159. package/ts-refs/trans-render/XV/types.d.ts +0 -69
  160. package/ts-refs/trans-render/asmr/types.d.ts +0 -138
  161. package/ts-refs/trans-render/be/types.d.ts +0 -190
  162. package/ts-refs/trans-render/dss/types.d.ts +0 -57
  163. package/ts-refs/trans-render/froop/types.d.ts +0 -416
  164. package/ts-refs/trans-render/funions/types.d.ts +0 -12
  165. package/ts-refs/trans-render/lib/mixins/types.d.ts +0 -42
  166. package/ts-refs/trans-render/lib/prs/types.d.ts +0 -40
  167. package/ts-refs/trans-render/lib/types.d.ts +0 -489
  168. package/ts-refs/trans-render/types.d.ts +0 -583
  169. package/ts-refs/wc-info/SimpleWCInfo.d.ts +0 -15
  170. package/ts-refs/when-resolved/types.d.ts +0 -30
  171. package/ts-refs/xp-as/types.d.ts +0 -20
  172. package/ts-refs/xtal-element/types.d.ts +0 -43
  173. package/ts-refs/xtal-frappe-chart/types.d.ts +0 -193
  174. package/upShadowSearch.js +0 -25
  175. package/upShadowSearch.ts +0 -23
  176. package/waitForEvent.js +0 -12
  177. package/waitForEvent.ts +0 -13
  178. package/waitForIsh.js +0 -21
  179. package/waitForIsh.ts +0 -20
package/loadImports.js ADDED
@@ -0,0 +1,47 @@
1
+ // Dynamic import loading utilities
2
+ // Only loaded when MountInit.import is specified
3
+ export async function loadImports(imports) {
4
+ const importArray = Array.isArray(imports) ? imports : [imports];
5
+ const promises = importArray.map(imp => loadSingleImport(imp));
6
+ return Promise.all(promises);
7
+ }
8
+ async function loadSingleImport(imp) {
9
+ let url;
10
+ let type = 'js';
11
+ if (typeof imp === 'string') {
12
+ url = imp;
13
+ }
14
+ else if (Array.isArray(imp)) {
15
+ url = imp[0];
16
+ type = imp[1]?.type || 'js';
17
+ }
18
+ else {
19
+ url = imp.url;
20
+ type = imp.type || 'js';
21
+ }
22
+ switch (type) {
23
+ case 'css':
24
+ return loadCSS(url);
25
+ case 'json':
26
+ return loadJSON(url);
27
+ case 'html':
28
+ return loadHTML(url);
29
+ default:
30
+ return import(url);
31
+ }
32
+ }
33
+ async function loadCSS(url) {
34
+ const response = await fetch(url);
35
+ const text = await response.text();
36
+ const sheet = new CSSStyleSheet();
37
+ await sheet.replace(text);
38
+ return sheet;
39
+ }
40
+ async function loadJSON(url) {
41
+ const response = await fetch(url);
42
+ return response.json();
43
+ }
44
+ async function loadHTML(url) {
45
+ const response = await fetch(url);
46
+ return response.text();
47
+ }
package/loadImports.ts ADDED
@@ -0,0 +1,56 @@
1
+ // Dynamic import loading utilities
2
+ // Only loaded when MountInit.import is specified
3
+
4
+ import { ImportSpec } from './types.js';
5
+
6
+ export async function loadImports(
7
+ imports: string | ImportSpec | Array<string | ImportSpec | [string, any]>
8
+ ): Promise<any[]> {
9
+ const importArray = Array.isArray(imports) ? imports : [imports];
10
+ const promises = importArray.map(imp => loadSingleImport(imp));
11
+ return Promise.all(promises);
12
+ }
13
+
14
+ async function loadSingleImport(imp: string | ImportSpec | [string, any]): Promise<any> {
15
+ let url: string;
16
+ let type: string = 'js';
17
+
18
+ if (typeof imp === 'string') {
19
+ url = imp;
20
+ } else if (Array.isArray(imp)) {
21
+ url = imp[0];
22
+ type = imp[1]?.type || 'js';
23
+ } else {
24
+ url = imp.url;
25
+ type = imp.type || 'js';
26
+ }
27
+
28
+ switch (type) {
29
+ case 'css':
30
+ return loadCSS(url);
31
+ case 'json':
32
+ return loadJSON(url);
33
+ case 'html':
34
+ return loadHTML(url);
35
+ default:
36
+ return import(url);
37
+ }
38
+ }
39
+
40
+ async function loadCSS(url: string): Promise<CSSStyleSheet> {
41
+ const response = await fetch(url);
42
+ const text = await response.text();
43
+ const sheet = new CSSStyleSheet();
44
+ await sheet.replace(text);
45
+ return sheet;
46
+ }
47
+
48
+ async function loadJSON(url: string): Promise<any> {
49
+ const response = await fetch(url);
50
+ return response.json();
51
+ }
52
+
53
+ async function loadHTML(url: string): Promise<string> {
54
+ const response = await fetch(url);
55
+ return response.text();
56
+ }
package/package.json CHANGED
@@ -1,133 +1,26 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.0.111",
3
+ "version": "0.1.0",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
7
+ "dependencies": {
8
+ "assign-gingerly": "0.0.2"
9
+ },
7
10
  "devDependencies": {
8
- "@playwright/test": "1.57.0",
9
- "spa-ssi": "0.0.25"
11
+ "@playwright/test": "1.58.0",
12
+ "spa-ssi": "0.0.26"
10
13
  },
11
14
  "exports": {
12
15
  ".": {
13
16
  "default": "./MountObserver.js",
14
17
  "types": "./MountObserver.ts"
15
- },
16
- "./MountObserver.js": {
17
- "default": "./MountObserver.js",
18
- "types": "./MountObserver.ts"
19
- },
20
- "./Newish.js": {
21
- "default": "./Newish.js",
22
- "types": "./Newish.ts"
23
- },
24
- "./ObsAttr.js": {
25
- "default": "./ObsAttr.js",
26
- "types": "./ObsAttr.ts"
27
- },
28
- "./Synthesizer.js": {
29
- "default": "./Synthesizer.js",
30
- "types": "./Synthesizer.ts"
31
- },
32
- "./bindish.js": {
33
- "default": "./bindish.js",
34
- "types": "./bindish.ts"
35
- },
36
- "./compose.js": {
37
- "default": "./compose.js",
38
- "types": "./compose.ts"
39
- },
40
- "./doCleanup.js": {
41
- "default": "./doCleanup.js",
42
- "types": "./doCleanup.ts"
43
- },
44
- "./upShadowSearch.js": {
45
- "default": "./upShadowSearch.js",
46
- "types": "./upShadowSearch.ts"
47
- },
48
- "./waitForEvent.js": {
49
- "default": "./waitForEvent.js",
50
- "types": "./waitForEvent.ts"
51
- },
52
- "./waitForIsh.js": {
53
- "default": "./waitForIsh.js",
54
- "types": "./waitForIsh.ts"
55
- },
56
- "./refid/camelToKebab.js": {
57
- "default": "./refid/camelToKebab.js",
58
- "types": "./refid/camelToKebab.ts"
59
- },
60
- "./refid/refid/genIds.js": {
61
- "default": "./refid/refid/genIds.js",
62
- "types": "./refid/refid/genIds.ts"
63
- },
64
- "./refid/getAdjRefs.js": {
65
- "default": "./refid/getAdjRefs.js",
66
- "types": "./refid/getAdjRefs.ts"
67
- },
68
- "./refid/getContext.js": {
69
- "default": "./refid/getContext.js",
70
- "types": "./refid/getContext.ts"
71
- },
72
- "./refid/getCount.js": {
73
- "default": "./refid/getCount.js",
74
- "types": "./refid/getCount.ts"
75
- },
76
- "./refid/ism.js": {
77
- "default": "./refid/ism.js",
78
- "types": "./refid/ism.ts"
79
- },
80
- "./refid/itemprops.js": {
81
- "default": "./refid/itemprops.js",
82
- "types": "./refid/itemprops.ts"
83
- },
84
- "./refid/joinMatching.js": {
85
- "default": "./refid/joinMatching.js",
86
- "types": "./refid/joinMatching.ts"
87
- },
88
- "./refid/nudge.js": {
89
- "default": "./refid/nudge.js",
90
- "types": "./refid/nudge.ts"
91
- },
92
- "./refid/refs.js": {
93
- "default": "./refid/refs.js",
94
- "types": "./refid/refs.ts"
95
- },
96
- "./refid/regIsh.js": {
97
- "default": "./refid/regIsh.js",
98
- "types": "./refid/regIsh.ts"
99
- },
100
- "./refid/via.js": {
101
- "default": "./refid/via.js",
102
- "types": "./refid/via.ts"
103
- },
104
- "./refid/splitRefs.js": {
105
- "default": "./refid/splitRefs.js",
106
- "types": "./refid/splitRefs.ts"
107
- },
108
- "./slotkin/beKindred.js": {
109
- "default": "./slotkin/beKindred.js",
110
- "types": "./slotkin/beKindred.ts"
111
- },
112
- "./slotkin/getBreadth.js": {
113
- "default": "./slotkin/getBreadth.js",
114
- "types": "./slotkin/getBreadth.ts"
115
- },
116
- "./slotkin/getFrag.js": {
117
- "default": "./slotkin/getFrag.js",
118
- "types": "./slotkin/getFrag.ts"
119
- },
120
- "./slotkin/wrap.js": {
121
- "default": "./slotkin/wrap.js",
122
- "types": "./slotkin/wrap.ts"
123
18
  }
19
+
124
20
  },
125
21
  "files": [
126
22
  "*.js",
127
- "*.ts",
128
- "./ts-refs/*",
129
- "./refid/*",
130
- "./slotkin/*"
23
+ "*.ts"
131
24
  ],
132
25
  "types": "./ts-refs/mount-observer/types.d.ts",
133
26
  "scripts": {
@@ -15,7 +15,6 @@ const config: PlaywrightTestConfig = {
15
15
  name: 'chromium',
16
16
  use: { ...devices['Desktop Chrome'] },
17
17
  },
18
- //firefox lacks support for import assertions
19
18
  {
20
19
  name: 'firefox',
21
20
  use: { ...devices['Desktop Firefox'] },
package/types.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ // Core types for MountObserver v2 - Polyfill Supported Scenario I
2
+
3
+ export interface MountInit {
4
+ whereElementMatches: string;
5
+ whereAttr?: WhereAttr;
6
+ import?: string | ImportSpec | Array<string | ImportSpec>;
7
+ do?: DoCallback | DoCallbacks;
8
+ loadingEagerness?: 'eager' | 'lazy';
9
+ assignGingerly?: Record<string, any>;
10
+ map?: MapConfig;
11
+ }
12
+
13
+ export interface MapConfig {
14
+ [coordinate: string]: MapEntry;
15
+ }
16
+
17
+ export interface MapEntry {
18
+ instanceOf?: string;
19
+ mapsTo?: string;
20
+ [key: string]: any;
21
+ }
22
+
23
+ export type BranchValue = string | { [key: string]: BranchValue[] };
24
+
25
+ export interface WhereAttr {
26
+ hasBuiltInRootIn?: string[];
27
+ hasCERootIn?: string[];
28
+ hasBase: string;
29
+ hasBranchIn?: BranchValue[];
30
+ }
31
+
32
+ export interface ImportSpec {
33
+ url: string;
34
+ type?: 'js' | 'css' | 'json' | 'html';
35
+ }
36
+
37
+ export interface MountContext {
38
+ modules: any[];
39
+ observer: IMountObserver;
40
+ observeInfo: ObserveInfo;
41
+ }
42
+
43
+ export interface ObserveInfo {
44
+ rootNode: Node;
45
+ }
46
+
47
+ export type DoCallback = (matchingElement: Element, context: MountContext) => void;
48
+
49
+ export interface DoCallbacks {
50
+ mount?: (matchingElement: Element, context: MountContext) => void;
51
+ dismount?: (matchingElement: Element, context: MountContext) => void;
52
+ disconnect?: (matchingElement: Element, context: MountContext) => void;
53
+ reconnect?: (matchingElement: Element, context: MountContext) => void;
54
+ }
55
+
56
+ export interface MountObserverOptions {
57
+ disconnectedSignal?: AbortSignal;
58
+ }
59
+
60
+ export interface IMountObserver extends EventTarget {
61
+ observe(rootNode: Node): Promise<void>;
62
+ disconnect(): void;
63
+ disconnectedSignal: AbortSignal;
64
+ }
65
+
66
+ export interface IMountEvent extends Event {
67
+ matchingElement: Element;
68
+ modules: any[];
69
+ }
70
+
71
+ export interface IDismountEvent extends Event {
72
+ matchingElement: Element;
73
+ }
74
+
75
+ export interface IAttrChangeEvent extends Event {
76
+ changes: AttrChange[];
77
+ }
78
+
79
+ export interface AttrChange {
80
+ value: string | null;
81
+ attrNode: Attr | null;
82
+ mapEntry: MapEntry | null;
83
+ attrName: string;
84
+ coordinate: string;
85
+ element: Element;
86
+ }
package/whereAttr.js ADDED
@@ -0,0 +1,174 @@
1
+ const selectorCache = new WeakMap();
2
+ /**
3
+ * Checks if an element matches the whereAttr configuration using CSS selector matching
4
+ */
5
+ export function matchesWhereAttr(element, whereAttr) {
6
+ // Get or build the CSS selectors for this whereAttr config
7
+ let selectors = selectorCache.get(whereAttr);
8
+ if (!selectors) {
9
+ selectors = {
10
+ builtIn: buildWhereAttrSelector(false, whereAttr),
11
+ custom: buildWhereAttrSelector(true, whereAttr)
12
+ };
13
+ selectorCache.set(whereAttr, selectors);
14
+ }
15
+ // Determine which selector to use based on element type
16
+ const isCustomElement = element.tagName.toLowerCase().includes('-');
17
+ const selector = isCustomElement ? selectors.custom : selectors.builtIn;
18
+ // Use native CSS matching - optimized in Chrome/Blink
19
+ return element.matches(selector);
20
+ }
21
+ /**
22
+ * Builds a CSS selector string from the whereAttr configuration
23
+ * @param isCustomElement - Whether to build selector for custom elements (true) or built-in elements (false)
24
+ */
25
+ function buildWhereAttrSelector(isCustomElement, whereAttr) {
26
+ const rootPrefixes = isCustomElement
27
+ ? (whereAttr.hasCERootIn || [])
28
+ : (whereAttr.hasBuiltInRootIn || []);
29
+ // Parse base attribute for custom delimiter
30
+ const { delimiter: baseDelimiter, name: baseName } = parseDelimiter(whereAttr.hasBase);
31
+ const selectors = [];
32
+ // Build selectors for each valid prefix
33
+ for (const prefix of rootPrefixes) {
34
+ const baseAttrName = buildAttributeName(prefix, baseName, baseDelimiter);
35
+ const escapedBaseAttr = escapeAttributeName(baseAttrName);
36
+ // If no branches specified, just having the base attribute is enough
37
+ if (!whereAttr.hasBranchIn || whereAttr.hasBranchIn.length === 0) {
38
+ selectors.push(`[${escapedBaseAttr}]`);
39
+ continue;
40
+ }
41
+ // Build selectors for each branch combination
42
+ for (const branch of whereAttr.hasBranchIn) {
43
+ if (branch === '') {
44
+ // Empty string means base attribute alone is valid (no branch attributes)
45
+ // This requires base attr AND none of the branch attrs
46
+ // For CSS, we can only check for base attr presence
47
+ // The "no branch attrs" check needs to be done separately
48
+ selectors.push(`[${escapedBaseAttr}]`);
49
+ continue;
50
+ }
51
+ if (typeof branch === 'object') {
52
+ // Build selectors for each branch path
53
+ for (const [key, subBranches] of Object.entries(branch)) {
54
+ const branchSelectors = buildBranchSelectors(baseAttrName, key, subBranches);
55
+ selectors.push(...branchSelectors);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ // Join all selectors with comma (OR logic)
61
+ return selectors.join(',');
62
+ }
63
+ /**
64
+ * Builds CSS selectors for a branch and its sub-branches
65
+ */
66
+ function buildBranchSelectors(baseAttrName, branchKey, subBranches) {
67
+ const { delimiter: branchDelimiter, name: branchName } = parseDelimiter(branchKey);
68
+ const branchAttrName = baseAttrName + branchDelimiter + branchName;
69
+ const escapedBranchAttr = escapeAttributeName(branchAttrName);
70
+ const selectors = [];
71
+ // If no sub-branches specified, just the branch attribute itself
72
+ if (!subBranches || subBranches.length === 0) {
73
+ selectors.push(`[${escapedBranchAttr}]`);
74
+ return selectors;
75
+ }
76
+ // Build selectors for each sub-branch - they form an OR condition
77
+ for (const subBranch of subBranches) {
78
+ if (subBranch === '') {
79
+ // Empty string means this branch level alone is valid
80
+ selectors.push(`[${escapedBranchAttr}]`);
81
+ continue;
82
+ }
83
+ if (typeof subBranch === 'string') {
84
+ // Simple string sub-branch - build the full path
85
+ const { delimiter: subDelimiter, name: subName } = parseDelimiter(subBranch);
86
+ const subAttrName = branchAttrName + subDelimiter + subName;
87
+ const escapedSubAttr = escapeAttributeName(subAttrName);
88
+ selectors.push(`[${escapedSubAttr}]`);
89
+ continue;
90
+ }
91
+ if (typeof subBranch === 'object') {
92
+ // Nested object sub-branch - recursively build deeper paths
93
+ for (const [key, nestedBranches] of Object.entries(subBranch)) {
94
+ const nestedSelectors = buildNestedBranchSelectors(branchAttrName, key, nestedBranches);
95
+ selectors.push(...nestedSelectors);
96
+ }
97
+ }
98
+ }
99
+ return selectors;
100
+ }
101
+ /**
102
+ * Builds CSS selectors for nested branches recursively
103
+ */
104
+ function buildNestedBranchSelectors(parentAttrName, branchKey, subBranches) {
105
+ const { delimiter, name } = parseDelimiter(branchKey);
106
+ const attrName = parentAttrName + delimiter + name;
107
+ const escapedAttr = escapeAttributeName(attrName);
108
+ const selectors = [];
109
+ // If no sub-branches specified, just this attribute
110
+ if (!subBranches || subBranches.length === 0) {
111
+ selectors.push(`[${escapedAttr}]`);
112
+ return selectors;
113
+ }
114
+ // Build selectors for each sub-branch - they form an OR condition
115
+ for (const subBranch of subBranches) {
116
+ if (subBranch === '') {
117
+ // Empty string means this level alone is valid
118
+ selectors.push(`[${escapedAttr}]`);
119
+ continue;
120
+ }
121
+ if (typeof subBranch === 'string') {
122
+ // Simple string sub-branch - build the full path
123
+ const { delimiter: subDelimiter, name: subName } = parseDelimiter(subBranch);
124
+ const subAttrName = attrName + subDelimiter + subName;
125
+ const escapedSubAttr = escapeAttributeName(subAttrName);
126
+ selectors.push(`[${escapedSubAttr}]`);
127
+ continue;
128
+ }
129
+ if (typeof subBranch === 'object') {
130
+ // Nested object - recursively build deeper paths
131
+ for (const [key, nestedBranches] of Object.entries(subBranch)) {
132
+ const nestedSelectors = buildNestedBranchSelectors(attrName, key, nestedBranches);
133
+ selectors.push(...nestedSelectors);
134
+ }
135
+ }
136
+ }
137
+ return selectors;
138
+ }
139
+ /**
140
+ * Escapes special characters in attribute names for CSS selectors
141
+ * Uses CSS.escape() API which handles all special characters including :
142
+ */
143
+ function escapeAttributeName(attrName) {
144
+ // CSS.escape() is available in all modern browsers
145
+ // It properly escapes special characters like : . [ ] etc.
146
+ return CSS.escape(attrName);
147
+ }
148
+ /**
149
+ * Parses a key to extract custom delimiter and name
150
+ * Format: [delimiter]name
151
+ * Example: "[_]my-custom" returns { delimiter: "_", name: "my-custom" }
152
+ */
153
+ function parseDelimiter(key) {
154
+ const match = key.match(/^\[(.+?)\](.+)$/);
155
+ if (match) {
156
+ return {
157
+ delimiter: match[1],
158
+ name: match[2]
159
+ };
160
+ }
161
+ return {
162
+ delimiter: '-',
163
+ name: key
164
+ };
165
+ }
166
+ /**
167
+ * Builds the full attribute name from prefix, base name, and delimiter
168
+ */
169
+ function buildAttributeName(prefix, baseName, delimiter) {
170
+ if (prefix === '') {
171
+ return baseName;
172
+ }
173
+ return prefix + delimiter + baseName;
174
+ }