diff --git a/packages/styling/src/Style.tsx b/packages/styling/src/Style.tsx
index d8cde74..40ece6a 100644
--- a/packages/styling/src/Style.tsx
+++ b/packages/styling/src/Style.tsx
@@ -9,15 +9,15 @@ type Props = {
precedence?: string;
};
-const Style = ({ children, href, precedence = 'medium' }: Props) => {
+const Style = ({ children, href: _href, precedence = 'medium' }: Props) => {
// Minify CSS styles by replacing consecutive whitespace (including \n) with ' '
- const styles = useStyles(children);
+ const { href, styles } = useStyles(children, _href);
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/canary.d.ts
// https://react.dev/reference/react-dom/components/style#props
return (
// @ts-expect-error @types/react is missing new
);
diff --git a/packages/styling/src/useStyles.test.tsx b/packages/styling/src/useStyles.test.tsx
index d597433..c5f0a8e 100644
--- a/packages/styling/src/useStyles.test.tsx
+++ b/packages/styling/src/useStyles.test.tsx
@@ -60,4 +60,11 @@ describe('@acusti/styling', () => {
expect(styleRegistry.size).toBe(0);
});
});
+
+ it('should sanitize styles used as href prop if no href prop provided', () => {
+ render();
+ // the two-dash attribute selector results in “Range out of order in character class”
+ // and render() fails with SyntaxError: Invalid regular expression if not sanitized
+ expect(true).toBeTruthy();
+ });
});
diff --git a/packages/styling/src/useStyles.ts b/packages/styling/src/useStyles.ts
index 3c0bb9c..536bc20 100644
--- a/packages/styling/src/useStyles.ts
+++ b/packages/styling/src/useStyles.ts
@@ -2,42 +2,58 @@ import { useEffect, useState } from 'react';
import { minifyStyles } from './minifyStyles.js';
-type StyleRegistry = Map;
+type StyleRegistry = Map<
+ string,
+ { href: string; referenceCount: number; styles: string }
+>;
const styleRegistry: StyleRegistry = new Map();
export const getStyleRegistry = () => styleRegistry;
-export function useStyles(styles: string) {
- const [minifiedStyles, setMinifiedStyles] = useState(() => {
- if (!styles) return '';
+export function useStyles(styles: string, initialHref?: string) {
+ const [stylesItem, setStylesItem] = useState(() => {
+ if (!styles) return { href: '', referenceCount: 0, styles: '' };
- let minified = '';
- const existingStylesItem = styleRegistry.get(styles);
- if (existingStylesItem) {
- existingStylesItem.referenceCount++;
- minified = existingStylesItem.styles;
+ const key = initialHref ?? styles;
+ let item = styleRegistry.get(key);
+
+ if (item) {
+ item.referenceCount++;
} else {
- minified = minifyStyles(styles);
- styleRegistry.set(styles, { referenceCount: 1, styles: minified });
+ const minified = minifyStyles(styles);
+ item = {
+ href: sanitizeHref(initialHref ?? minified),
+ referenceCount: 1,
+ styles: minified,
+ };
+ styleRegistry.set(key, item);
}
- return minified;
+ return item;
});
useEffect(() => {
if (!styles) return;
- if (!styleRegistry.get(styles)) {
+
+ const key = initialHref ?? styles;
+
+ if (!styleRegistry.get(key)) {
const minified = minifyStyles(styles);
- styleRegistry.set(styles, { referenceCount: 1, styles: minified });
- setMinifiedStyles(minified);
+ const item = {
+ href: sanitizeHref(initialHref ?? minified),
+ referenceCount: 1,
+ styles: minified,
+ };
+ styleRegistry.set(key, item);
+ setStylesItem(item);
}
return () => {
- const stylesItem = styleRegistry.get(styles);
- if (stylesItem) {
- stylesItem.referenceCount--;
- if (!stylesItem.referenceCount) {
+ const existingItem = styleRegistry.get(styles);
+ if (existingItem) {
+ existingItem.referenceCount--;
+ if (!existingItem.referenceCount) {
// TODO try scheduling this via setTimeout
// and add another referenceCount check
// to deal with instance where existing