Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v0.2.31] - 2026-01-08

### Added
- Add `createStyleScope()` for scoped dynamic CSS

### Changed
- Use `@aegisjsproject/escape` for HTML escaping

Expand Down
2 changes: 1 addition & 1 deletion core.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export {
text, createStyleSheet, createCSSParser, css, lightCSS, darkCSS ,
styleSheetToFile, styleSheetToLink, createHTMLParser, html, doc, trustedHTML,
htmlUnsafe, docUnsafe, htmlToFile, createTrustedHTMLTemplate, xml, svg, json, math, url,
createShadowParser, shadow, styledShadow, el, createBoundParser, adoptStyles, prefixCSSRules,
createShadowParser, shadow, styledShadow, el, createBoundParser, adoptStyles, prefixCSSRules, createStyleScope,
} from './parsers.js';

export {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aegisjsproject/core",
"version": "0.2.30",
"version": "0.2.31",
"description": "A fast, secure, modern, light-weight, and simple JS library for creating web components and more!",
"keywords": [
"aegis",
Expand Down
2 changes: 1 addition & 1 deletion parsers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { text } from './parsers/text.js';
export {
createStyleSheet, createCSSParser, css, lightCSS, prefixCSSRules,
darkCSS, styleSheetToFile, styleSheetToLink, createBoundParser, adoptStyles,
darkCSS, styleSheetToFile, styleSheetToLink, createBoundParser, adoptStyles, createStyleScope,
} from './parsers/css.js';
export {
createHTMLParser, html, doc, htmlUnsafe, docUnsafe, htmlToFile,
Expand Down
37 changes: 37 additions & 0 deletions parsers/css.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
import { createStyleSheet, createCSSParser, css, setStyleSheets, addStyleSheets } from '@aegisjsproject/parsers/css.js';

const PREFIX = '_aegis_style_scope_';

/**
* Creates a scoped CSSStyleSheet attached to the provided root and returns a
* tagged template function for generating unique class names within that scope.
*
* @param {Document | ShadowRoot | Element} [root=document] The DOM scope to attach the stylesheet to.
* If an Element is passed, its root node (Document or ShadowRoot) is used.
*
* @param {Object} [options] Configuration options.
* @param {string} [options.baseURL] The base URL used to resolve relative URLs in the stylesheet.
* @param {string|MediaList|MediaQueryList} [options.media] The intended media for the stylesheet (e.g., "screen", "print").
* @param {boolean} [options.disabled] Whether the stylesheet is disabled by default.
* @param {string} [options.prefix] A custom prefix for generated class names.
* @returns {(strings: TemplateStringsArray, ...values: any[]) => string} A tagged template function that
* inserts a new CSS rule and returns the generated class name.
*/
export function createStyleScope(root = document, { baseURL, media, disabled, prefix = PREFIX } = {}) {
if (root instanceof Element) {
return createStyleScope(root.getRootNode(), { baseURL, media, disabled, prefix });
} else if (! (root instanceof Document || root instanceof ShadowRoot)) {
throw new TypeError('Root must be a Document, ShadowRoot, or Element.');
} else {
const sheet = new CSSStyleSheet({ baseURL, media: media instanceof MediaQueryList ? media.media : media, disabled });
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];

return (strings, ...values) => {
const uuid = crypto.randomUUID();
const className = `${prefix}${uuid}`;
const rule = `.${CSS.escape(prefix) + uuid} { ${String.raw(strings, ...values)} }`;
sheet.insertRule(rule, sheet.cssRules.length);

return className;
};
}
}

export const lightCSS = createCSSParser({ media: '(prefers-color-scheme: light)', baseURL: document.baseURI });

export const darkCSS = createCSSParser({ media: '(prefers-color-scheme: dark)', baseURL: document.baseURI });
Expand Down
11 changes: 8 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
html, css, replaceStyles, getUniqueSelector, createComponent, closeRegistration,
data, attr, createTrustedHTMLTemplate,
data, attr, createTrustedHTMLTemplate, createStyleScope,
} from '@aegisjsproject/core';

import { sanitizer as defaultSanitizer } from '@aegisjsproject/sanitizer/config/html.js';
Expand Down Expand Up @@ -42,7 +42,7 @@ document.body.setAttribute(fooEvent, FUNCS.debug.log);
document.body.dataset[stateKey] = 'bg';
document.body.dataset[stateStyle] = 'background-color';

replaceStyles(document, reset, baseTheme, lightTheme, darkTheme, btn, btnPrimary, btnDanger, btnWarning,
replaceStyles(document, ...document.adoptedStyleSheets, reset, baseTheme, lightTheme, darkTheme, btn, btnPrimary, btnDanger, btnWarning,
btnInfo, btnSystemAccent, btnSuccess, btnLink, btnSecondary,
css`.${scope} {
color: red;
Expand All @@ -57,6 +57,8 @@ replaceStyles(document, reset, baseTheme, lightTheme, darkTheme, btn, btnPrimary
}
`);

const style = createStyleScope(document.body, { media: matchMedia('(min-width: 800px)')});

const DadJoke = await customElements.whenDefined('dad-joke');
const frag = document.createDocumentFragment();
const h1 = document.createElement('h1');
Expand All @@ -66,7 +68,10 @@ h1.textContent = 'Hello, World!';
frag.append(h1);

try {
document.body.append(html`<header onclick="alert(location)" foo="bar">
document.body.append(html`<header class="${style`
background-color: rgb(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
`}" onclick="alert(location)" foo="bar">
${frag}
<hello-world></hello-world><h1 foo="bar">Click Me!</h1>
<svg viewBox="0 0 10 10" height="24" width="24">
Expand Down
Loading