@jsenv/dom 0.1.0 → 0.3.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.
@@ -0,0 +1,153 @@
1
+ import { normalizeStyle } from "./style_parsing.js";
2
+
3
+ const DEBUG = false;
4
+
5
+ // Register the style isolator custom element once
6
+ let persistentStyleIsolator = null;
7
+ const getNaviStyleIsolator = () => {
8
+ if (persistentStyleIsolator) {
9
+ return persistentStyleIsolator;
10
+ }
11
+
12
+ class StyleIsolator extends HTMLElement {
13
+ constructor() {
14
+ super();
15
+
16
+ // Create shadow DOM to isolate from external CSS
17
+ const shadow = this.attachShadow({ mode: "closed" });
18
+
19
+ shadow.innerHTML = `
20
+ <style>
21
+ :host {
22
+ all: initial;
23
+ display: block;
24
+ position: fixed;
25
+ top: 0;
26
+ left: 0;
27
+ opacity: ${DEBUG ? 0.5 : 0};
28
+ visibility: ${DEBUG ? "visible" : "hidden"};
29
+ pointer-events: none;
30
+ }
31
+ * {
32
+ all: revert;
33
+ }
34
+ </style>
35
+ <div id="unstyled_element_slot"></div>
36
+ `;
37
+
38
+ this.unstyledElementSlot = shadow.querySelector("#unstyled_element_slot");
39
+ }
40
+
41
+ getIsolatedStyles(element, context = "js") {
42
+ if (!DEBUG) {
43
+ this.unstyledElementSlot.innerHTML = "";
44
+ }
45
+ const unstyledElement = element.cloneNode(true);
46
+ this.unstyledElementSlot.appendChild(unstyledElement);
47
+
48
+ // Get computed styles of the actual element inside the shadow DOM
49
+ const computedStyles = getComputedStyle(unstyledElement);
50
+ // Create a copy of the styles since the original will be invalidated when element is removed
51
+ const stylesCopy = {};
52
+ for (let i = 0; i < computedStyles.length; i++) {
53
+ const property = computedStyles[i];
54
+ stylesCopy[property] = normalizeStyle(
55
+ computedStyles.getPropertyValue(property),
56
+ property,
57
+ context,
58
+ );
59
+ }
60
+
61
+ return stylesCopy;
62
+ }
63
+ }
64
+
65
+ if (!customElements.get("navi-style-isolator")) {
66
+ customElements.define("navi-style-isolator", StyleIsolator);
67
+ }
68
+ // Create and add the persistent element to the document
69
+ persistentStyleIsolator = document.createElement("navi-style-isolator");
70
+ document.body.appendChild(persistentStyleIsolator);
71
+ return persistentStyleIsolator;
72
+ };
73
+
74
+ const stylesCache = new Map();
75
+ /**
76
+ * Gets the default browser styles for an HTML element by creating an isolated custom element
77
+ * @param {string|Element} input - CSS selector (e.g., 'input[type="text"]'), HTML source (e.g., '<button>'), or DOM element
78
+ * @param {string} context - Output format: "js" for JS object (default) or "css" for CSS string
79
+ * @returns {Object|string} Computed styles as JS object or CSS string
80
+ */
81
+ export const getDefaultStyles = (input, context = "js") => {
82
+ let element;
83
+ let cacheKey;
84
+
85
+ // Determine input type and create element accordingly
86
+ if (typeof input === "string") {
87
+ if (input[0] === "<") {
88
+ // HTML source
89
+ const tempDiv = document.createElement("div");
90
+ tempDiv.innerHTML = input;
91
+ element = tempDiv.firstElementChild;
92
+ if (!element) {
93
+ throw new Error(`Invalid HTML source: ${input}`);
94
+ }
95
+ cacheKey = `${input}:${context}`;
96
+ } else {
97
+ // CSS selector
98
+ element = createElementFromSelector(input);
99
+ cacheKey = `${input}:${context}`;
100
+ }
101
+ } else if (input instanceof Element) {
102
+ // DOM element
103
+ element = input;
104
+ cacheKey = `${input.outerHTML}:${context}`;
105
+ } else {
106
+ throw new Error(
107
+ "Input must be a CSS selector, HTML source, or DOM element",
108
+ );
109
+ }
110
+
111
+ // Check cache first
112
+ if (stylesCache.has(cacheKey)) {
113
+ return stylesCache.get(cacheKey);
114
+ }
115
+
116
+ // Get the persistent style isolator element
117
+ const naviStyleIsolator = getNaviStyleIsolator();
118
+ const defaultStyles = naviStyleIsolator.getIsolatedStyles(element, context);
119
+
120
+ // Cache the result
121
+ stylesCache.set(cacheKey, defaultStyles);
122
+
123
+ return defaultStyles;
124
+ };
125
+
126
+ /**
127
+ * Creates an HTML element from a CSS selector
128
+ * @param {string} selector - CSS selector (e.g., 'input[type="text"]', 'button', 'a[href="#"]')
129
+ * @returns {Element} DOM element
130
+ */
131
+ const createElementFromSelector = (selector) => {
132
+ // Parse the selector to extract tag name and attributes
133
+ const tagMatch = selector.match(/^([a-zA-Z][a-zA-Z0-9-]*)/);
134
+ if (!tagMatch) {
135
+ throw new Error(`Invalid selector: ${selector}`);
136
+ }
137
+
138
+ const tagName = tagMatch[1].toLowerCase();
139
+ const element = document.createElement(tagName);
140
+
141
+ // Extract and apply attributes from selector
142
+ const attributeRegex = /\[([^=\]]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\]]*)))?\]/g;
143
+ let attributeMatch;
144
+
145
+ while ((attributeMatch = attributeRegex.exec(selector)) !== null) {
146
+ const attrName = attributeMatch[1];
147
+ const attrValue =
148
+ attributeMatch[2] || attributeMatch[3] || attributeMatch[4] || "";
149
+ element.setAttribute(attrName, attrValue);
150
+ }
151
+
152
+ return element;
153
+ };
@@ -0,0 +1,128 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Button Background-Color Demo</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ margin: 40px;
11
+ line-height: 1.6;
12
+ }
13
+
14
+ .demo-section {
15
+ background: white;
16
+ padding: 20px;
17
+ margin: 20px 0;
18
+ border: 1px solid #ddd;
19
+ border-radius: 8px;
20
+ }
21
+
22
+ .result {
23
+ background: #f8f8f8;
24
+ border: 1px solid #ddd;
25
+ padding: 15px;
26
+ margin: 10px 0;
27
+ font-family: monospace;
28
+ font-size: 14px;
29
+ }
30
+
31
+ .result h3 {
32
+ margin-top: 0;
33
+ font-family: Arial, sans-serif;
34
+ }
35
+
36
+ .color-preview {
37
+ display: inline-block;
38
+ width: 20px;
39
+ height: 20px;
40
+ border: 1px solid #333;
41
+ vertical-align: middle;
42
+ margin-left: 8px;
43
+ }
44
+
45
+ /* Intentionally style buttons to test isolation */
46
+ button {
47
+ background: red !important;
48
+ color: white !important;
49
+ border: 3px solid blue !important;
50
+ padding: 10px 20px;
51
+ margin: 10px;
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <h1>Button Background-Color Demo</h1>
57
+ <p>
58
+ This demo tests <code>getDefaultStyles('button')</code> and focuses on the
59
+ background-color property.
60
+ </p>
61
+
62
+ <div class="demo-section">
63
+ <h2>Styled Button on Page</h2>
64
+ <p>This button is styled with CSS (red background):</p>
65
+ <button>Styled Button</button>
66
+ <p>
67
+ <em
68
+ >The button above should be red, proving our page CSS is working.</em
69
+ >
70
+ </p>
71
+ </div>
72
+
73
+ <div class="demo-section">
74
+ <h2>Default Button Background-Color Test</h2>
75
+ <div id="result"></div>
76
+ </div>
77
+
78
+ <script type="module">
79
+ import { getDefaultStyles } from "./style_default.js";
80
+
81
+ // Make function available globally for onclick
82
+ window.testButtonBackgroundColor = testButtonBackgroundColor;
83
+
84
+ function testButtonBackgroundColor() {
85
+ const resultDiv = document.getElementById("result");
86
+
87
+ try {
88
+ console.log("Testing button default background-color...");
89
+ const startTime = performance.now();
90
+
91
+ // Get default styles for button
92
+ const styles = getDefaultStyles("button");
93
+
94
+ const endTime = performance.now();
95
+ const duration = (endTime - startTime).toFixed(2);
96
+
97
+ // Extract background-color
98
+ const backgroundColor = styles["background-color"];
99
+
100
+ // Display result
101
+ resultDiv.innerHTML = `
102
+ <div class="result">
103
+ <h3>Result (${duration}ms)</h3>
104
+ <p><strong>Element:</strong> button</p>
105
+ <p><strong>background-color:</strong> <code>${backgroundColor}</code> <span class="color-preview" style="background-color: ${backgroundColor};" title="Color preview: ${backgroundColor}"></span></p>
106
+ <p><strong>Expected:</strong> Should be a light color (not red), proving CSS isolation works</p>
107
+ <p><strong>Full styles object keys:</strong> ${Object.keys(styles).length} properties</p>
108
+ </div>
109
+ `;
110
+
111
+ console.log("Button default background-color:", backgroundColor);
112
+ console.log("Full styles object:", styles);
113
+ } catch (error) {
114
+ console.error("Error:", error);
115
+ resultDiv.innerHTML = `
116
+ <div class="result" style="background: #ffe6e6;">
117
+ <h3>Error</h3>
118
+ <p>${error.message}</p>
119
+ </div>
120
+ `;
121
+ }
122
+ }
123
+
124
+ // Auto-run test on page load
125
+ setTimeout(testButtonBackgroundColor, 500);
126
+ </script>
127
+ </body>
128
+ </html>
@@ -0,0 +1,27 @@
1
+ import StyleObserver from "style-observer";
2
+
3
+ import { normalizeStyle } from "./style_parsing.js";
4
+
5
+ export const styleEffect = (element, callback, properties = []) => {
6
+ const check = () => {
7
+ const values = {};
8
+ const computedStyle = getComputedStyle(element);
9
+ for (const property of properties) {
10
+ values[property] = normalizeStyle(
11
+ computedStyle.getPropertyValue(property),
12
+ property,
13
+ );
14
+ }
15
+ callback(values);
16
+ };
17
+
18
+ check();
19
+ const observer = new StyleObserver(() => {
20
+ check();
21
+ });
22
+ observer.observe(element, properties);
23
+
24
+ return () => {
25
+ observer.unobserve();
26
+ };
27
+ };
@@ -136,6 +136,9 @@ const normalizeNumber = (value, context, unit, propertyName) => {
136
136
  if (value === "auto") {
137
137
  return "auto";
138
138
  }
139
+ if (value === "none") {
140
+ return "none";
141
+ }
139
142
  const numericValue = parseFloat(value);
140
143
  if (isNaN(numericValue)) {
141
144
  console.warn(