@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.
- package/dist/jsenv_dom.js +4121 -1944
- package/index.js +13 -0
- package/package.json +4 -2
- package/src/color/color_constrast.js +69 -0
- package/src/color/color_parsing.js +319 -0
- package/src/color/color_scheme.js +28 -0
- package/src/color/pick_light_or_dark.js +34 -0
- package/src/color/resolve_css_color.js +60 -0
- package/src/position/visible_rect.js +6 -2
- package/src/pub_sub.js +4 -1
- package/src/style/style_default.js +153 -0
- package/src/style/style_default_demo.html +128 -0
- package/src/style/style_effect.js +27 -0
- package/src/style/style_parsing.js +3 -0
|
@@ -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(
|