Repository: microsoft/griffel (ts)

Issue:
Add shorthand function for `inset`
Right now there's no shorthand in `@griffer/core` for [`inset`](https://developer.mozilla.org/en-US/docs/Web/CSS/Inset)

Setting `top`, `bottom`, `left`, and `right` for an absolute positioned element is a fairly common pattern. It would be great to expose a shorthand equivalent for this newer CSS rule.

### How it should work?
`inset` is CSS shorthand that is expanded to the `top`, `bottom`, `left`, and `right` properties. It follows the same format as `margin` and `padding`, so the logic from those shorthand functions can be reused.


Pull request:
feat: adds inset shorthand
Resolves #73 

Patch:
diff --git a/apps/website/docs/react/api/make-styles.md b/apps/website/docs/react/api/make-styles.md
index 5577977..f341ba6 100644
--- a/apps/website/docs/react/api/make-styles.md
+++ b/apps/website/docs/react/api/make-styles.md
@@ -122,7 +122,7 @@ html[data-whatintent='mouse'] .f1xz3i88 {
 
 ### At-rules
 
-`@media` & `@supports` queries are also supported:
+`@media`, `@supports` & `@layer` queries are also supported:
 
 ```js
 import { makeStyles } from '@griffel/react';
@@ -131,6 +131,7 @@ const useClasses = makeStyles({
   root: {
     '@media screen and (max-width: 992px)': { color: 'orange' },
     '@supports (display: grid)': { color: 'red' },
+    '@layer utility': { marginBottom: '1em' }
   },
 });
 ```
@@ -148,6 +149,12 @@ const useClasses = makeStyles({
     color: red;
   }
 }
+
+@layer utility {
+  .f2d3jla {
+    margin-bottom: 1em;
+  }
+}
 ```
 
 ### `@keyframes` (animations)
diff --git a/beachball.config.js b/beachball.config.js
index 1472bfb..cd0b916 100644
--- a/beachball.config.js
+++ b/beachball.config.js
@@ -5,5 +5,14 @@
  */
 module.exports = {
   gitTags: false,
+  ignorePatterns: [
+    '**/__fixtures__/**',
+    '**/*.test.{ts,tsx}',
+    '**/*.stories.tsx',
+    '**/.eslintrc.json',
+    '**/jest.config.js',
+    '**/project.json',
+    '**/README.md',
+  ],
   hooks: require('./beachball.hooks'),
 };
diff --git a/change/@griffel-babel-preset-41b92af0-6e5b-4e53-b341-0bd5b8407b76.json b/change/@griffel-babel-preset-41b92af0-6e5b-4e53-b341-0bd5b8407b76.json
new file mode 100644
index 0000000..28279be
--- /dev/null
+++ b/change/@griffel-babel-preset-41b92af0-6e5b-4e53-b341-0bd5b8407b76.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix: handle duplicated import sources properly",
+  "packageName": "@griffel/babel-preset",
+  "email": "rezha@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-9132796e-b0a7-46a9-b70f-72bef61024b9.json b/change/@griffel-core-9132796e-b0a7-46a9-b70f-72bef61024b9.json
new file mode 100644
index 0000000..27a4ba3
--- /dev/null
+++ b/change/@griffel-core-9132796e-b0a7-46a9-b70f-72bef61024b9.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat: add support for @layer",
+  "packageName": "@griffel/core",
+  "email": "mgodbolt@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-e9730721-98ad-4971-8976-6f934c35cf1f.json b/change/@griffel-core-e9730721-98ad-4971-8976-6f934c35cf1f.json
new file mode 100644
index 0000000..54e01ad
--- /dev/null
+++ b/change/@griffel-core-e9730721-98ad-4971-8976-6f934c35cf1f.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "Adds inset shorthand",
+  "packageName": "@griffel/core",
+  "email": "bsunderhus@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-react-6d0f6f43-4864-4ba4-b292-8e46b9d1d20f.json b/change/@griffel-react-6d0f6f43-4864-4ba4-b292-8e46b9d1d20f.json
new file mode 100644
index 0000000..02b672d
--- /dev/null
+++ b/change/@griffel-react-6d0f6f43-4864-4ba4-b292-8e46b9d1d20f.json
@@ -0,0 +1,7 @@
+{
+  "type": "none",
+  "comment": "docs: add support for @layer",
+  "packageName": "@griffel/react",
+  "email": "mgodbolt@microsoft.com",
+  "dependentChangeType": "none"
+}
diff --git a/packages/babel-preset/__fixtures__/duplicated-imports/code.ts b/packages/babel-preset/__fixtures__/duplicated-imports/code.ts
new file mode 100644
index 0000000..8c4eb21
--- /dev/null
+++ b/packages/babel-preset/__fixtures__/duplicated-imports/code.ts
@@ -0,0 +1,12 @@
+// @ts-expect-error This module does not exist, but will be resolved via aliases
+import { createStylesA } from 'custom-package';
+// @ts-expect-error This module does not exist, but will be resolved via aliases
+import { createStylesB } from 'custom-package';
+
+export const useClassesA = createStylesA({
+  root: { color: 'red' },
+});
+
+export const useClassesB = createStylesB({
+  root: { color: 'yellow' },
+});
diff --git a/packages/babel-preset/__fixtures__/duplicated-imports/options.json b/packages/babel-preset/__fixtures__/duplicated-imports/options.json
new file mode 100644
index 0000000..d8906ae
--- /dev/null
+++ b/packages/babel-preset/__fixtures__/duplicated-imports/options.json
@@ -0,0 +1,3 @@
+{
+  "modules": [{ "moduleSource": "custom-package", "importName": "createStylesA" }]
+}
diff --git a/packages/babel-preset/__fixtures__/duplicated-imports/output.ts b/packages/babel-preset/__fixtures__/duplicated-imports/output.ts
new file mode 100644
index 0000000..16a685f
--- /dev/null
+++ b/packages/babel-preset/__fixtures__/duplicated-imports/output.ts
@@ -0,0 +1,19 @@
+// @ts-expect-error This module does not exist, but will be resolved via aliases
+import { __styles } from 'custom-package'; // @ts-expect-error This module does not exist, but will be resolved via aliases
+
+import { createStylesB } from 'custom-package';
+export const useClassesA = __styles(
+  {
+    root: {
+      sj55zd: 'fe3e8s9',
+    },
+  },
+  {
+    d: ['.fe3e8s9{color:red;}'],
+  },
+);
+export const useClassesB = createStylesB({
+  root: {
+    color: 'yellow',
+  },
+});
diff --git a/packages/babel-preset/src/transformPlugin.ts b/packages/babel-preset/src/transformPlugin.ts
index 0d7d5ba..bdf3dae 100644
--- a/packages/babel-preset/src/transformPlugin.ts
+++ b/packages/babel-preset/src/transformPlugin.ts
@@ -10,7 +10,7 @@ import { BabelPluginOptions } from './types';
 import { validateOptions } from './validateOptions';
 
 type BabelPluginState = PluginPass & {
-  importDeclarationPath?: NodePath<t.ImportDeclaration>;
+  importDeclarationPaths?: NodePath<t.ImportDeclaration>[];
   requireDeclarationPath?: NodePath<t.VariableDeclarator>;
 
   definitionPaths?: NodePath<t.ObjectExpression>[];
@@ -130,6 +130,7 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
     name: '@griffel/babel-plugin-transform',
 
     pre() {
+      this.importDeclarationPaths = [];
       this.definitionPaths = [];
       this.calleePaths = [];
     },
@@ -142,7 +143,7 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
         },
 
         exit(path, state) {
-          if (!state.importDeclarationPath && !state.requireDeclarationPath) {
+          if (state.importDeclarationPaths!.length === 0 && !state.requireDeclarationPath) {
             return;
           }
 
@@ -172,9 +173,9 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
             });
           }
 
-          if (state.importDeclarationPath) {
-            const specifiers = state.importDeclarationPath.get('specifiers');
-            const source = state.importDeclarationPath.get('source');
+          state.importDeclarationPaths!.forEach(importDeclarationPath => {
+            const specifiers = importDeclarationPath.get('specifiers');
+            const source = importDeclarationPath.get('source');
 
             specifiers.forEach(specifier => {
               if (specifier.isImportSpecifier()) {
@@ -194,7 +195,7 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
                 }
               }
             });
-          }
+          });
 
           if (state.calleePaths) {
             state.calleePaths.forEach(calleePath => {
@@ -207,7 +208,7 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
       // eslint-disable-next-line @typescript-eslint/naming-convention
       ImportDeclaration(path, state) {
         if (hasMakeStylesImport(path, pluginOptions.modules)) {
-          state.importDeclarationPath = path;
+          state.importDeclarationPaths!.push(path);
         }
       },
 
@@ -225,7 +226,7 @@ export const transformPlugin = declare<Partial<BabelPluginOptions>, PluginObj<Ba
          *
          * @example makeStyles({})
          */
-        if (!state.importDeclarationPath) {
+        if (state.importDeclarationPaths!.length === 0) {
           return;
         }
 
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 0278a1b..4eab908 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -14,6 +14,7 @@ import {
   margin,
   padding,
   overflow,
+  inset,
 } from './shorthands/index';
 
 export const shorthands = {
@@ -30,6 +31,7 @@ export const shorthands = {
   margin,
   padding,
   overflow,
+  inset,
 };
 
 export { createDOMRenderer } from './renderer/createDOMRenderer';
diff --git a/packages/core/src/renderer/rehydrateRendererCache.ts b/packages/core/src/renderer/rehydrateRendererCache.ts
index 9dd34f5..5ba2986 100644
--- a/packages/core/src/renderer/rehydrateRendererCache.ts
+++ b/packages/core/src/renderer/rehydrateRendererCache.ts
@@ -3,7 +3,7 @@ import { GriffelRenderer, StyleBucketName } from '../types';
 // Regexps to extract names of classes and animations
 // https://github.com/styletron/styletron/blob/e0fcae826744eb00ce679ac613a1b10d44256660/packages/styletron-engine-atomic/src/client/client.js#L8
 const KEYFRAMES_HYDRATOR = /@(-webkit-)?keyframes ([^{]+){((?:(?:from|to|(?:\d+\.?\d*%))\{(?:[^}])*})*)}/g;
-const AT_RULES_HYDRATOR = /@(media|supports)[^{]+\{([\s\S]+?})\s*}/g;
+const AT_RULES_HYDRATOR = /@(media|supports|layer)[^{]+\{([\s\S]+?})\s*}/g;
 const STYLES_HYDRATOR = /\.([^{:]+)(:[^{]+)?{(?:[^}]*;)?([^}]*?)}/g;
 
 const regexps: Partial<Record<StyleBucketName, RegExp>> = {
diff --git a/packages/core/src/runtime/compileCSS.ts b/packages/core/src/runtime/compileCSS.ts
index 8dfcb5b..11adaeb 100644
--- a/packages/core/src/runtime/compileCSS.ts
+++ b/packages/core/src/runtime/compileCSS.ts
@@ -8,6 +8,7 @@ export interface CompileCSSOptions {
 
   pseudo: string;
   media: string;
+  layer: string;
   support: string;
 
   property: string;
@@ -60,7 +61,7 @@ export function compileCSSRules(cssRules: string): string[] {
 }
 
 export function compileCSS(options: CompileCSSOptions): [string /* ltr definition */, string? /* rtl definition */] {
-  const { className, media, pseudo, support, property, rtlClassName, rtlProperty, rtlValue, value } = options;
+  const { className, media, layer, pseudo, support, property, rtlClassName, rtlProperty, rtlValue, value } = options;
 
   const classNameSelector = `.${className}`;
   const cssDeclaration = Array.isArray(value)
@@ -109,6 +110,10 @@ export function compileCSS(options: CompileCSSOptions): [string /* ltr definitio
     cssRule = `@media ${media} { ${cssRule} }`;
   }
 
+  if (layer) {
+    cssRule = `@layer ${layer} { ${cssRule} }`;
+  }
+
   if (support) {
     cssRule = `@supports ${support} { ${cssRule} }`;
   }
diff --git a/packages/core/src/runtime/getStyleBucketName.ts b/packages/core/src/runtime/getStyleBucketName.ts
index ac4bfa3..1bab581 100644
--- a/packages/core/src/runtime/getStyleBucketName.ts
+++ b/packages/core/src/runtime/getStyleBucketName.ts
@@ -40,9 +40,9 @@ const pseudosMap: Record<string, StyleBucketName | undefined> = {
  * "f"
  * ```
  */
-export function getStyleBucketName(pseudo: string, media: string, support: string): StyleBucketName {
+export function getStyleBucketName(pseudo: string, layer: string, media: string, support: string): StyleBucketName {
   // We are grouping all the at-rules like @media, @supports etc under `t` bucket.
-  if (media || support) {
+  if (media || layer || support) {
     return 't';
   }
 
diff --git a/packages/core/src/runtime/resolveStyleRules.ts b/packages/core/src/runtime/resolveStyleRules.ts
index 588c27c..6a368db 100644
--- a/packages/core/src/runtime/resolveStyleRules.ts
+++ b/packages/core/src/runtime/resolveStyleRules.ts
@@ -7,6 +7,7 @@ import { compileCSS, CompileCSSOptions } from './compileCSS';
 import { compileKeyframeRule, compileKeyframesCSS } from './compileKeyframeCSS';
 import { generateCombinedQuery } from './utils/generateCombinedMediaQuery';
 import { isMediaQuerySelector } from './utils/isMediaQuerySelector';
+import { isLayerSelector } from './utils/isLayerSelector';
 import { isNestedSelector } from './utils/isNestedSelector';
 import { isSupportQuerySelector } from './utils/isSupportQuerySelector';
 import { normalizeNestedProperty } from './utils/normalizeNestedProperty';
@@ -48,6 +49,7 @@ export function resolveStyleRules(
   styles: GriffelStyle,
   pseudo = '',
   media = '',
+  layer = '',
   support = '',
   cssClassesMap: CSSClassesMap = {},
   cssRulesByBucket: CSSRulesByBucket = {},
@@ -85,6 +87,7 @@ export function resolveStyleRules(
       const key = hashPropertyKey(pseudo, media, support, property);
       const className = hashClassName({
         media,
+        layer,
         value: value.toString(),
         support,
         pseudo,
@@ -100,6 +103,7 @@ export function resolveStyleRules(
             property: rtlDefinition.key,
             pseudo,
             media,
+            layer,
             support,
           })
         : undefined;
@@ -111,10 +115,11 @@ export function resolveStyleRules(
           }
         : undefined;
 
-      const styleBucketName = getStyleBucketName(pseudo, media, support);
+      const styleBucketName = getStyleBucketName(pseudo, layer, media, support);
       const [ltrCSS, rtlCSS] = compileCSS({
         className,
         media,
+        layer,
         pseudo,
         property,
         support,
@@ -166,6 +171,7 @@ export function resolveStyleRules(
         { animationName: animationNames.join(', ') },
         pseudo,
         media,
+        layer,
         support,
         cssClassesMap,
         cssRulesByBucket,
@@ -185,6 +191,7 @@ export function resolveStyleRules(
       const key = hashPropertyKey(pseudo, media, support, property);
       const className = hashClassName({
         media,
+        layer,
         value: value.map(v => (v ?? '').toString()).join(';'),
         support,
         pseudo,
@@ -211,6 +218,7 @@ export function resolveStyleRules(
             value: rtlDefinitions.map(v => (v?.value ?? '').toString()).join(';'),
             property: rtlDefinitions[0].key,
             pseudo,
+            layer,
             media,
             support,
           })
@@ -224,10 +232,11 @@ export function resolveStyleRules(
           }
         : undefined;
 
-      const styleBucketName = getStyleBucketName(pseudo, media, support);
+      const styleBucketName = getStyleBucketName(pseudo, layer, media, support);
       const [ltrCSS, rtlCSS] = compileCSS({
         className,
         media,
+        layer,
         pseudo,
         property,
         support,
@@ -243,6 +252,7 @@ export function resolveStyleRules(
           value as GriffelStyle,
           pseudo + normalizeNestedProperty(property),
           media,
+          layer,
           support,
           cssClassesMap,
           cssRulesByBucket,
@@ -250,11 +260,39 @@ export function resolveStyleRules(
       } else if (isMediaQuerySelector(property)) {
         const combinedMediaQuery = generateCombinedQuery(media, property.slice(6).trim());
 
-        resolveStyleRules(value as GriffelStyle, pseudo, combinedMediaQuery, support, cssClassesMap, cssRulesByBucket);
+        resolveStyleRules(
+          value as GriffelStyle,
+          pseudo,
+          combinedMediaQuery,
+          layer,
+          support,
+          cssClassesMap,
+          cssRulesByBucket,
+        );
+      } else if (isLayerSelector(property)) {
+        const combinedLayerQuery = (layer ? `${layer}.` : '') + property.slice(6).trim();
+
+        resolveStyleRules(
+          value as GriffelStyle,
+          pseudo,
+          media,
+          combinedLayerQuery,
+          support,
+          cssClassesMap,
+          cssRulesByBucket,
+        );
       } else if (isSupportQuerySelector(property)) {
         const combinedSupportQuery = generateCombinedQuery(support, property.slice(9).trim());
 
-        resolveStyleRules(value as GriffelStyle, pseudo, media, combinedSupportQuery, cssClassesMap, cssRulesByBucket);
+        resolveStyleRules(
+          value as GriffelStyle,
+          pseudo,
+          media,
+          layer,
+          combinedSupportQuery,
+          cssClassesMap,
+          cssRulesByBucket,
+        );
       } else {
         if (process.env.NODE_ENV !== 'production') {
           // eslint-disable-next-line no-console
diff --git a/packages/core/src/runtime/utils/hashClassName.ts b/packages/core/src/runtime/utils/hashClassName.ts
index c2cf369..70a602a 100644
--- a/packages/core/src/runtime/utils/hashClassName.ts
+++ b/packages/core/src/runtime/utils/hashClassName.ts
@@ -6,12 +6,13 @@ export interface HashedClassNameParts {
   value: string;
   pseudo: string;
   media: string;
+  layer: string;
   support: string;
 }
 
-export function hashClassName({ media, property, pseudo, support, value }: HashedClassNameParts): string {
+export function hashClassName({ media, layer, property, pseudo, support, value }: HashedClassNameParts): string {
   // Trimming of value is required to generate consistent hashes
-  const classNameHash = hashString(pseudo + media + support + property + value.trim());
+  const classNameHash = hashString(pseudo + media + layer + support + property + value.trim());
 
   return HASH_PREFIX + classNameHash;
 }
diff --git a/packages/core/src/runtime/utils/isLayerSelector.ts b/packages/core/src/runtime/utils/isLayerSelector.ts
new file mode 100644
index 0000000..85c531a
--- /dev/null
+++ b/packages/core/src/runtime/utils/isLayerSelector.ts
@@ -0,0 +1,3 @@
+export function isLayerSelector(property: string): boolean {
+  return property.substr(0, 6) === '@layer';
+}
diff --git a/packages/core/src/shorthands/index.ts b/packages/core/src/shorthands/index.ts
index 00d26fa..b398d95 100644
--- a/packages/core/src/shorthands/index.ts
+++ b/packages/core/src/shorthands/index.ts
@@ -11,3 +11,4 @@ export { gap } from './gap';
 export { margin } from './margin';
 export { padding } from './padding';
 export { overflow } from './overflow';
+export { inset } from './inset';
diff --git a/packages/core/src/shorthands/inset.ts b/packages/core/src/shorthands/inset.ts
new file mode 100644
index 0000000..1f8247b
--- /dev/null
+++ b/packages/core/src/shorthands/inset.ts
@@ -0,0 +1,30 @@
+import type { GriffelStylesStrictCSSObject } from '../types';
+import { InsetInput } from './types';
+
+type InsetStyle = Pick<GriffelStylesStrictCSSObject, 'top' | 'right' | 'bottom' | 'left'>;
+
+export function inset(all: InsetInput): InsetStyle;
+export function inset(vertical: InsetInput, horizontal: InsetInput): InsetStyle;
+export function inset(top: InsetInput, horizontal: InsetInput, bottom: InsetInput): InsetStyle;
+export function inset(top: InsetInput, right: InsetInput, bottom: InsetInput, left: InsetInput): InsetStyle;
+
+/**
+ * A function that implements CSS spec conformant expansion for "inset"
+ *
+ * @example
+ *   inset('10px')
+ *   inset('10px', '5px')
+ *   inset('2px', '4px', '8px')
+ *   inset('1px', 0, '3px', '4px')
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/CSS/inset
+ */
+export function inset(...values: InsetInput[]): InsetStyle {
+  const [firstValue, secondValue = firstValue, thirdValue = firstValue, fourthValue = secondValue] = values;
+  return {
+    top: firstValue,
+    right: secondValue,
+    bottom: thirdValue,
+    left: fourthValue,
+  };
+}
diff --git a/packages/core/src/shorthands/types.ts b/packages/core/src/shorthands/types.ts
index 3b79852..2ac2eb6 100644
--- a/packages/core/src/shorthands/types.ts
+++ b/packages/core/src/shorthands/types.ts
@@ -14,3 +14,5 @@ export type MarginInput = ValueOrArray<CSS.Property.Margin<GriffelStylesCSSValue
 export type OverflowInput = ValueOrArray<CSS.Property.Overflow>;
 
 export type PaddingInput = ValueOrArray<CSS.Property.Padding<GriffelStylesCSSValue>>;
+
+export type InsetInput = ValueOrArray<CSS.Property.Inset<GriffelStylesCSSValue>>;
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 401a054..ec71957 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -126,7 +126,7 @@ export type StyleBucketName =
   | 'a'
   // @keyframes definitions
   | 'k'
-  // at-rules (@media, @support)
+  // at-rules (@media, @support, @layer)
   | 't';
 
 export type SequenceHash = string;
diff --git a/packages/react/README.md b/packages/react/README.md
index b7534c3..8f1d3db 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -187,6 +187,7 @@ const useClasses = makeStyles({
 
     '@media screen and (max-width: 992px)': { color: 'orange' },
     '@supports (display: grid)': { color: 'red' },
+    '@layer utility': { marginBottom: '1em' }
   },
 });
 ```


Test patch:
diff --git a/packages/core/src/runtime/compileCSS.test.ts b/packages/core/src/runtime/compileCSS.test.ts
index ea4bc06..386a986 100644
--- a/packages/core/src/runtime/compileCSS.test.ts
+++ b/packages/core/src/runtime/compileCSS.test.ts
@@ -1,12 +1,14 @@
 import { compileCSS, CompileCSSOptions, normalizePseudoSelector } from './compileCSS';
 
-const defaultOptions: Pick<CompileCSSOptions, 'rtlClassName' | 'className' | 'media' | 'pseudo' | 'support'> = {
-  className: 'foo',
-  rtlClassName: 'rtl-foo',
-  media: '',
-  pseudo: '',
-  support: '',
-};
+const defaultOptions: Pick<CompileCSSOptions, 'rtlClassName' | 'className' | 'media' | 'pseudo' | 'support' | 'layer'> =
+  {
+    className: 'foo',
+    rtlClassName: 'rtl-foo',
+    media: '',
+    pseudo: '',
+    support: '',
+    layer: '',
+  };
 
 describe('compileCSS', () => {
   it('handles pseudo', () => {
diff --git a/packages/core/src/runtime/getStyleBucketName.test.ts b/packages/core/src/runtime/getStyleBucketName.test.ts
index e18f439..93239d6 100644
--- a/packages/core/src/runtime/getStyleBucketName.test.ts
+++ b/packages/core/src/runtime/getStyleBucketName.test.ts
@@ -2,18 +2,19 @@ import { getStyleBucketName } from './getStyleBucketName';
 
 describe('getStyleBucketName', () => {
   it('returns bucketName based on mapping', () => {
-    expect(getStyleBucketName(' :link', '', '')).toBe('l');
-    expect(getStyleBucketName(' :hover ', '', '')).toBe('h');
+    expect(getStyleBucketName(' :link', '', '', '')).toBe('l');
+    expect(getStyleBucketName(' :hover ', '', '', '')).toBe('h');
 
-    expect(getStyleBucketName(':link', '', '')).toBe('l');
-    expect(getStyleBucketName(':visited', '', '')).toBe('v');
-    expect(getStyleBucketName(':focus-within', '', '')).toBe('w');
-    expect(getStyleBucketName(':focus', '', '')).toBe('f');
-    expect(getStyleBucketName(':focus-visible', '', '')).toBe('i');
-    expect(getStyleBucketName(':hover', '', '')).toBe('h');
-    expect(getStyleBucketName(':active', '', '')).toBe('a');
+    expect(getStyleBucketName(':link', '', '', '')).toBe('l');
+    expect(getStyleBucketName(':visited', '', '', '')).toBe('v');
+    expect(getStyleBucketName(':focus-within', '', '', '')).toBe('w');
+    expect(getStyleBucketName(':focus', '', '', '')).toBe('f');
+    expect(getStyleBucketName(':focus-visible', '', '', '')).toBe('i');
+    expect(getStyleBucketName(':hover', '', '', '')).toBe('h');
+    expect(getStyleBucketName(':active', '', '', '')).toBe('a');
 
-    expect(getStyleBucketName(':active', '(max-width: 100px)', '')).toBe('t');
-    expect(getStyleBucketName(':active', '', '(display: table-cell)')).toBe('t');
+    expect(getStyleBucketName(':active', 'theme', '', '')).toBe('t');
+    expect(getStyleBucketName(':active', '', '(max-width: 100px)', '')).toBe('t');
+    expect(getStyleBucketName(':active', '', '', '(display: table-cell)')).toBe('t');
   });
 });
diff --git a/packages/core/src/runtime/resolveStyleRules.test.ts b/packages/core/src/runtime/resolveStyleRules.test.ts
index 97938ba..3ceaa78 100644
--- a/packages/core/src/runtime/resolveStyleRules.test.ts
+++ b/packages/core/src/runtime/resolveStyleRules.test.ts
@@ -345,7 +345,7 @@ describe('resolveStyleRules', () => {
       `);
     });
 
-    it('handles media queries with preudo selectors', () => {
+    it('handles media queries with pseudo selectors', () => {
       expect(
         resolveStyleRules({
           color: 'green',
@@ -393,6 +393,85 @@ describe('resolveStyleRules', () => {
       `);
     });
 
+    it('handles layer queries', () => {
+      expect(
+        resolveStyleRules({
+          color: 'green',
+          '@layer color': { color: 'red' },
+        }),
+      ).toMatchInlineSnapshot(`
+        .fka9v86 {
+          color: green;
+        }
+        @layer color {
+          .f1hjcal7 {
+            color: red;
+          }
+        }
+      `);
+    });
+
+    it('handles layer queries with dots', () => {
+      expect(
+        resolveStyleRules({
+          '@layer framework.utilities': { color: 'red' },
+        }),
+      ).toMatchInlineSnapshot(`
+        @layer framework.utilities {
+          .faxdetk {
+            color: red;
+          }
+        }
+      `);
+    });
+    it('handles layer queries with pseudo selectors', () => {
+      expect(
+        resolveStyleRules({
+          color: 'green',
+          '@layer color': {
+            ':hover': {
+              color: 'red ',
+            },
+          },
+        }),
+      ).toMatchInlineSnapshot(`
+        .fka9v86 {
+          color: green;
+        }
+        @layer color {
+          .f1jv0g3z:hover {
+            color: red;
+          }
+        }
+      `);
+    });
+
+    it('handles nested layer queries', () => {
+      expect(
+        resolveStyleRules({
+          color: 'red',
+          '@layer color': {
+            color: 'red',
+            '@layer theme': { color: 'red' },
+          },
+        }),
+      ).toMatchInlineSnapshot(`
+        .fe3e8s9 {
+          color: red;
+        }
+        @layer color {
+          .f1hjcal7 {
+            color: red;
+          }
+        }
+        @layer color.theme {
+          .f132c7g3 {
+            color: red;
+          }
+        }
+      `);
+    });
+
     it('handles supports queries', () => {
       expect(
         resolveStyleRules({
diff --git a/packages/core/src/shorthands/inset.test.ts b/packages/core/src/shorthands/inset.test.ts
new file mode 100644
index 0000000..75aa674
--- /dev/null
+++ b/packages/core/src/shorthands/inset.test.ts
@@ -0,0 +1,57 @@
+import { inset } from './inset';
+
+describe('inset', () => {
+  it('for a given value', () => {
+    expect(inset('12px')).toEqual({
+      bottom: '12px',
+      left: '12px',
+      right: '12px',
+      top: '12px',
+    });
+  });
+
+  it('for given vertical and horizontal values', () => {
+    expect(inset('12px', '24px')).toEqual({
+      bottom: '12px',
+      left: '24px',
+      right: '24px',
+      top: '12px',
+    });
+  });
+
+  it('for given top, horizontal and bottom values', () => {
+    expect(inset('12px', '24px', '36px')).toEqual({
+      bottom: '36px',
+      left: '24px',
+      right: '24px',
+      top: '12px',
+    });
+  });
+
+  it('for given top, right, bottom and left values', () => {
+    expect(inset('12px', '24px', '36px', '48px')).toEqual({
+      bottom: '36px',
+      left: '48px',
+      right: '24px',
+      top: '12px',
+    });
+  });
+
+  it('for a given zero value', () => {
+    expect(inset(0)).toEqual({
+      bottom: 0,
+      left: 0,
+      right: 0,
+      top: 0,
+    });
+  });
+
+  it('for fallback value arrays', () => {
+    expect(inset([0, '12px'], ['auto', '-2px'])).toEqual({
+      bottom: [0, '12px'],
+      left: ['auto', '-2px'],
+      right: ['auto', '-2px'],
+      top: [0, '12px'],
+    });
+  });
+});