Repository: microsoft/griffel
Pull request #385 (2023-07-07 13:01:27)

fix(shadow-dom): update sorting logic
Fixes an issue sorting logic with `@media` queries in `@griffel/shadow-dom`. New test cases added.

github-actions[bot]: ## 📊 Bundle size report

| Package & Exports                                                                                                          |   Baseline (minified/GZIP) |                         PR |                                                                                                                                                                                                Change |
| :------------------------------------------------------------------------------------------------------------------------- | -------------------------: | -------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <samp>shadow-dom</samp> <br /> <abbr title='bundle-size/createShadowDOMRenderer.fixture.js'>createShadowDOMRenderer</abbr> | `3.189 kB`<br />`1.378 kB` | `3.202 kB`<br />`1.387 kB` | `13 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`9 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /> |

<details>
<summary>Unchanged fixtures</summary>

| Package & Exports                                                                                                            |        Size (minified/GZIP) |
| ---------------------------------------------------------------------------------------------------------------------------- | --------------------------: |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeResetStyles-runtime.fixture.js'>makeResetStyles (runtime)</abbr>       |  `19.05 kB`<br />`7.214 kB` |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeStyles.fixture.js'>makeStyles + mergeClasses (build time)</abbr>       |   `2.19 kB`<br />`1.045 kB` |
| <samp>core</samp> <br /> <abbr title='bundle-size/makeStylesRuntime.fixture.js'>makeStyles + mergeClasses (runtime)</abbr>   | `23.999 kB`<br />`9.032 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/__css.fixture.js'>\_\_css + mergeClasses (build time)</abbr>              |  `2.227 kB`<br />`1.046 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/__styles.fixture.js'>\_\_styles + mergeClasses (build time)</abbr>        |  `4.045 kB`<br />`1.844 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeResetStyles-runtime.fixture.js'>makeResetStyles (runtime)</abbr>      | `20.929 kB`<br />`8.009 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStaticStyles-runtime.fixture.js'>makeStaticStyles (runtime)</abbr>    | `10.904 kB`<br />`4.656 kB` |
| <samp>react</samp> <br /> <abbr title='bundle-size/makeStyles-runtime.fixture.js'>makeStyles + mergeClasses (runtime)</abbr> | `25.883 kB`<br />`9.802 kB` |

</details>
<sub>🤖 This report was generated against <a href='https://github.com/microsoft/griffel/commit/73cdcaac6d98d80cf3cf492f93bde5f1f92990dc'>73cdcaac6d98d80cf3cf492f93bde5f1f92990dc</a></sub>

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/change/@griffel-shadow-dom-5e958d15-5e5b-415a-88dc-4ce5d171ab18.json b/change/@griffel-shadow-dom-5e958d15-5e5b-415a-88dc-4ce5d171ab18.json
new file mode 100644
index 0000000..5bb364e
--- /dev/null
+++ b/change/@griffel-shadow-dom-5e958d15-5e5b-415a-88dc-4ce5d171ab18.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix: update sorting logic",
+  "packageName": "@griffel/shadow-dom",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/packages/shadow-dom/src/createShadowDOMRenderer.ts b/packages/shadow-dom/src/createShadowDOMRenderer.ts
index 7314b14..d073f15 100644
--- a/packages/shadow-dom/src/createShadowDOMRenderer.ts
+++ b/packages/shadow-dom/src/createShadowDOMRenderer.ts
@@ -40,18 +40,18 @@ function getCSSStyleSheetForBucket(
   return cssSheetsCache[styleSheetKey];
 }
 
-function insertAfter<T extends CSSStyleSheet | ExtendedCSSStyleSheet>(
+function insertBefore<T extends CSSStyleSheet | ExtendedCSSStyleSheet>(
   arr: T[],
   sheetToInsert: T,
   targetSheet: T | null,
 ): T[] {
   if (targetSheet === null) {
-    return [sheetToInsert, ...arr];
+    return [...arr, sheetToInsert];
   }
 
   const index = arr.indexOf(targetSheet);
 
-  return [...arr.slice(0, index + 1), sheetToInsert, ...arr.slice(index + 1)];
+  return [...arr.slice(0, index), sheetToInsert, ...arr.slice(index)];
 }
 
 export function createShadowDOMRenderer(shadowRoot: ShadowRoot) {
@@ -85,8 +85,8 @@ export function createShadowDOMRenderer(shadowRoot: ShadowRoot) {
             styleSheet => {
               const targetStyleSheet = findInsertionPoint(renderer, styleSheet);
 
-              renderer.adoptedStyleSheets = insertAfter(renderer.adoptedStyleSheets, styleSheet, targetStyleSheet);
-              shadowRoot.adoptedStyleSheets = insertAfter(shadowRoot.adoptedStyleSheets, styleSheet, targetStyleSheet);
+              renderer.adoptedStyleSheets = insertBefore(renderer.adoptedStyleSheets, styleSheet, targetStyleSheet);
+              shadowRoot.adoptedStyleSheets = insertBefore(shadowRoot.adoptedStyleSheets, styleSheet, targetStyleSheet);
             },
           );
 
diff --git a/packages/shadow-dom/src/findInsertionPoint.ts b/packages/shadow-dom/src/findInsertionPoint.ts
index 15f8dba..981340f 100644
--- a/packages/shadow-dom/src/findInsertionPoint.ts
+++ b/packages/shadow-dom/src/findInsertionPoint.ts
@@ -29,9 +29,13 @@ export function findInsertionPoint(
     const styleElement = styleSheets[i];
 
     if (comparer(styleElement) > 0) {
-      return styleElement;
+      return styleSheets[i + 1] ?? null;
     }
   }
 
+  if (styleSheets.length > 0) {
+    return styleSheets[0];
+  }
+
   return null;
 }
diff --git a/yarn.lock b/yarn.lock
index 6fb71b2..71ee42e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -20946,6 +20946,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"querystringify@npm:^2.1.1":
+  version: 2.2.0
+  resolution: "querystringify@npm:2.2.0"
+  checksum: 5641ea231bad7ef6d64d9998faca95611ed4b11c2591a8cae741e178a974f6a8e0ebde008475259abe1621cb15e692404e6b6626e927f7b849d5c09392604b15
+  languageName: node
+  linkType: hard
+
 "queue-microtask@npm:^1.2.2":
   version: 1.2.3
   resolution: "queue-microtask@npm:1.2.3"
@@ -24122,13 +24129,14 @@ __metadata:
   linkType: hard
 
 "tough-cookie@npm:^4.0.0":
-  version: 4.0.0
-  resolution: "tough-cookie@npm:4.0.0"
+  version: 4.1.3
+  resolution: "tough-cookie@npm:4.1.3"
   dependencies:
     psl: ^1.1.33
     punycode: ^2.1.1
-    universalify: ^0.1.2
-  checksum: 0891b37eb7d17faa3479d47f0dce2e3007f2583094ad272f2670d120fbcc3df3b0b0a631ba96ecad49f9e2297d93ff8995ce0d3292d08dd7eabe162f5b224d69
+    universalify: ^0.2.0
+    url-parse: ^1.5.3
+  checksum: c9226afff36492a52118432611af083d1d8493a53ff41ec4ea48e5b583aec744b989e4280bcf476c910ec1525a89a4a0f1cae81c08b18fb2ec3a9b3a72b91dcc
   languageName: node
   linkType: hard
 
@@ -24771,13 +24779,20 @@ __metadata:
   languageName: node
   linkType: hard
 
-"universalify@npm:^0.1.0, universalify@npm:^0.1.2":
+"universalify@npm:^0.1.0":
   version: 0.1.2
   resolution: "universalify@npm:0.1.2"
   checksum: 40cdc60f6e61070fe658ca36016a8f4ec216b29bf04a55dce14e3710cc84c7448538ef4dad3728d0bfe29975ccd7bfb5f414c45e7b78883567fb31b246f02dff
   languageName: node
   linkType: hard
 
+"universalify@npm:^0.2.0":
+  version: 0.2.0
+  resolution: "universalify@npm:0.2.0"
+  checksum: e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5
+  languageName: node
+  linkType: hard
+
 "universalify@npm:^2.0.0":
   version: 2.0.0
   resolution: "universalify@npm:2.0.0"
@@ -24923,6 +24938,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"url-parse@npm:^1.5.3":
+  version: 1.5.10
+  resolution: "url-parse@npm:1.5.10"
+  dependencies:
+    querystringify: ^2.1.1
+    requires-port: ^1.0.0
+  checksum: fbdba6b1d83336aca2216bbdc38ba658d9cfb8fc7f665eb8b17852de638ff7d1a162c198a8e4ed66001ddbf6c9888d41e4798912c62b4fd777a31657989f7bdf
+  languageName: node
+  linkType: hard
+
 "url@npm:^0.11.0":
   version: 0.11.0
   resolution: "url@npm:0.11.0"


Test patch:
diff --git a/packages/core/src/renderer/getStyleSheetForBucket.test.ts b/packages/core/src/renderer/getStyleSheetForBucket.test.ts
index 9afcc26..04685b6 100644
--- a/packages/core/src/renderer/getStyleSheetForBucket.test.ts
+++ b/packages/core/src/renderer/getStyleSheetForBucket.test.ts
@@ -62,6 +62,40 @@ describe('getStyleSheetForBucket', () => {
     expect(styleElementOrder).toEqual(styleBucketOrdering);
   });
 
+  it('finds a position in media queries', () => {
+    const target = createFakeDocument();
+    const renderer = createDOMRenderer();
+
+    getStyleSheetForBucket('d', target, null, renderer);
+    getStyleSheetForBucket('t', target, null, renderer);
+    getStyleSheetForBucket('c', target, null, renderer);
+
+    getStyleSheetForBucket('m', target, null, renderer, { m: 'screen and (prefers-reduced-motion: reduce)' });
+    getStyleSheetForBucket('m', target, null, renderer, { m: '(forced-colors: active)' });
+
+    expect(target.head.children).toMatchInlineSnapshot(`
+      HTMLCollection [
+        <style
+          data-make-styles-bucket="d"
+        />,
+        <style
+          data-make-styles-bucket="t"
+        />,
+        <style
+          data-make-styles-bucket="m"
+          media="(forced-colors: active)"
+        />,
+        <style
+          data-make-styles-bucket="m"
+          media="screen and (prefers-reduced-motion: reduce)"
+        />,
+        <style
+          data-make-styles-bucket="c"
+        />,
+      ]
+    `);
+  });
+
   it('sets "data-make-styles-bucket" attribute in order with media queries', () => {
     const target = createFakeDocument();
     const mediaQueryOrder = ['(max-width: 1px)', '(max-width: 2px)', '(max-width: 3px)', '(max-width: 4px)'];
diff --git a/packages/shadow-dom/src/createShadowDOMRenderer.test.ts b/packages/shadow-dom/src/createShadowDOMRenderer.test.ts
new file mode 100644
index 0000000..5991a8e
--- /dev/null
+++ b/packages/shadow-dom/src/createShadowDOMRenderer.test.ts
@@ -0,0 +1,81 @@
+import { createShadowDOMRenderer } from './createShadowDOMRenderer';
+
+describe('createShadowDOMRenderer', () => {
+  it('returns a renderer', () => {
+    const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
+
+    expect(createShadowDOMRenderer(shadowRoot)).toHaveProperty('adoptedStyleSheets');
+  });
+
+  describe('CSS insertion', () => {
+    const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
+    const renderer = createShadowDOMRenderer(shadowRoot);
+
+    // jsdom doesn't support adoptedStyleSheets yet
+    shadowRoot.adoptedStyleSheets = [];
+
+    renderer.insertCSSRules({ d: ['a {}'] });
+    renderer.insertCSSRules({ t: ['a {}'] });
+
+    renderer.insertCSSRules({
+      m: [[`a {}`, { m: '(forced-colors: active)' }]],
+    });
+    renderer.insertCSSRules({
+      m: [[`a {}`, { m: '(prefers-reduced-motion)' }]],
+    });
+
+    renderer.insertCSSRules({ h: ['a {}'] });
+    renderer.insertCSSRules({ f: ['a {}'] });
+    renderer.insertCSSRules({ a: ['a {}'] });
+    renderer.insertCSSRules({ v: ['a {}'] });
+    renderer.insertCSSRules({ l: ['a {}'] });
+
+    renderer.insertCSSRules({ t: ['a {}'] });
+
+    expect(renderer.adoptedStyleSheets?.map(sheet => ({ bucketName: sheet.bucketName, metadata: sheet.metadata })))
+      .toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "bucketName": "d",
+          "metadata": Object {},
+        },
+        Object {
+          "bucketName": "l",
+          "metadata": Object {},
+        },
+        Object {
+          "bucketName": "v",
+          "metadata": Object {},
+        },
+        Object {
+          "bucketName": "f",
+          "metadata": Object {},
+        },
+        Object {
+          "bucketName": "h",
+          "metadata": Object {},
+        },
+        Object {
+          "bucketName": "a",
+          "metadata": Object {},
+        },
+        Object {
+          "bucketName": "t",
+          "metadata": Object {},
+        },
+        Object {
+          "bucketName": "m",
+          "metadata": Object {
+            "m": "(forced-colors: active)",
+          },
+        },
+        Object {
+          "bucketName": "m",
+          "metadata": Object {
+            "m": "(prefers-reduced-motion)",
+          },
+        },
+      ]
+    `);
+  });
+});
diff --git a/packages/shadow-dom/src/findInsertionPoint.test.ts b/packages/shadow-dom/src/findInsertionPoint.test.ts
index 9787279..a674ef3 100644
--- a/packages/shadow-dom/src/findInsertionPoint.test.ts
+++ b/packages/shadow-dom/src/findInsertionPoint.test.ts
@@ -33,24 +33,28 @@ describe('findInsertionPoint', () => {
   });
 
   it('finds a position at beginning', () => {
-    const renderer = createRendererMock([createStyleSheetMock('d', {}), createStyleSheetMock('a', {})]);
+    const renderer = createRendererMock([
+      createStyleSheetMock('d', {}),
+      createStyleSheetMock('a', {}),
+      createStyleSheetMock('t', {}),
+    ]);
     const styleSheet = createStyleSheetMock('r', {});
 
-    expect(findInsertionPoint(renderer, styleSheet)).toBe(null);
+    expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 'd');
   });
 
   it('finds a position in middle', () => {
-    const renderer = createRendererMock([createStyleSheetMock('d', {}), createStyleSheetMock('a', {})]);
+    const renderer = createRendererMock([createStyleSheetMock('d', {}), createStyleSheetMock('t', {})]);
     const styleSheet = createStyleSheetMock('h', {});
 
-    expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 'd');
+    expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 't');
   });
 
   it('finds a position at end', () => {
     const renderer = createRendererMock([createStyleSheetMock('d', {}), createStyleSheetMock('a', {})]);
     const styleSheet = createStyleSheetMock('t', {});
 
-    expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 'a');
+    expect(findInsertionPoint(renderer, styleSheet)).toBe(null);
   });
 
   describe('media queries', () => {
@@ -65,21 +69,21 @@ describe('findInsertionPoint', () => {
       const renderer = createRendererMock([createStyleSheetMock('c', {})]);
       const styleSheet = createStyleSheetMock('m', { m: '(max-width: 3px)' });
 
-      expect(findInsertionPoint(renderer, styleSheet)).toBe(null);
+      expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 'c');
     });
 
     it('finds a position in middle', () => {
       const renderer = createRendererMock([createStyleSheetMock('t', {}), createStyleSheetMock('c', {})]);
       const styleSheet = createStyleSheetMock('m', { m: '(max-width: 3px)' });
 
-      expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 't');
+      expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 'c');
     });
 
     it('finds a position at end', () => {
       const renderer = createRendererMock([createStyleSheetMock('d', {}), createStyleSheetMock('a', {})]);
       const styleSheet = createStyleSheetMock('m', { m: '(max-width: 3px)' });
 
-      expect(findInsertionPoint(renderer, styleSheet)).toHaveProperty('bucketName', 'a');
+      expect(findInsertionPoint(renderer, styleSheet)).toBe(null);
     });
 
     it('finds a position in media queries', () => {
@@ -94,12 +98,24 @@ describe('findInsertionPoint', () => {
       const resultA = findInsertionPoint(renderer, createStyleSheetMock('m', { m: '(max-width: 2px)' }));
 
       expect(resultA).toHaveProperty('bucketName', 'm');
-      expect(resultA).toHaveProperty('metadata.m', '(max-width: 1px)');
+      expect(resultA).toHaveProperty('metadata.m', '(max-width: 3px)');
 
       const resultB = findInsertionPoint(renderer, createStyleSheetMock('m', { m: '(max-width: 4px)' }));
 
-      expect(resultB).toHaveProperty('bucketName', 'm');
-      expect(resultB).toHaveProperty('metadata.m', '(max-width: 3px)');
+      expect(resultB).toBe(null);
+    });
+
+    it('finds a position in media queries', () => {
+      const renderer = createRendererMock([
+        createStyleSheetMock('d', {}),
+        createStyleSheetMock('t', {}),
+        createStyleSheetMock('m', { m: '(prefers-reduced-motion: reduce)' }),
+      ]);
+
+      const resultA = findInsertionPoint(renderer, createStyleSheetMock('m', { m: '(forced-colors: active)' }));
+
+      expect(resultA).toHaveProperty('bucketName', 'm');
+      expect(resultA).toHaveProperty('metadata.m', '(prefers-reduced-motion: reduce)');
     });
   });
 });