@knighted/css 1.0.0-rc.3 → 1.0.0-rc.5

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/cjs/css.cjs CHANGED
@@ -9,6 +9,7 @@ exports.cssWithMeta = cssWithMeta;
9
9
  exports.compileVanillaModule = compileVanillaModule;
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const node_fs_1 = require("node:fs");
12
+ const node_url_1 = require("node:url");
12
13
  const dependency_tree_1 = __importDefault(require("dependency-tree"));
13
14
  const lightningcss_1 = require("lightningcss");
14
15
  const helpers_js_1 = require("./helpers.cjs");
@@ -35,6 +36,7 @@ async function cssWithMeta(entry, options = {}) {
35
36
  const chunk = await compileStyleModule(file, {
36
37
  cwd,
37
38
  peerResolver: options.peerResolver,
39
+ resolver: options.resolver,
38
40
  });
39
41
  if (chunk) {
40
42
  chunks.push(chunk);
@@ -105,13 +107,17 @@ function matchExtension(filePath, extensions) {
105
107
  const lower = filePath.toLowerCase();
106
108
  return extensions.find(ext => lower.endsWith(ext));
107
109
  }
108
- async function compileStyleModule(file, { cwd, peerResolver }) {
110
+ async function compileStyleModule(file, { cwd, peerResolver, resolver, }) {
109
111
  switch (file.ext) {
110
112
  case '.css':
111
113
  return node_fs_1.promises.readFile(file.path, 'utf8');
112
114
  case '.scss':
113
115
  case '.sass':
114
- return compileSass(file.path, file.ext === '.sass', peerResolver);
116
+ return compileSass(file.path, file.ext === '.sass', {
117
+ cwd,
118
+ peerResolver,
119
+ resolver,
120
+ });
115
121
  case '.less':
116
122
  return compileLess(file.path, peerResolver);
117
123
  case '.css.ts':
@@ -120,14 +126,145 @@ async function compileStyleModule(file, { cwd, peerResolver }) {
120
126
  return '';
121
127
  }
122
128
  }
123
- async function compileSass(filePath, indented, peerResolver) {
129
+ async function compileSass(filePath, indented, { cwd, peerResolver, resolver, }) {
124
130
  const sassModule = await optionalPeer('sass', 'Sass', peerResolver);
125
131
  const sass = sassModule;
126
- const result = sass.compile(filePath, {
132
+ const importer = createSassImporter({ cwd, resolver });
133
+ const result = await sass.compileAsync(filePath, {
127
134
  style: 'expanded',
135
+ loadPaths: buildSassLoadPaths(filePath),
136
+ importers: importer ? [importer] : undefined,
128
137
  });
129
138
  return result.css;
130
139
  }
140
+ // Ensure Sass can resolve bare module specifiers by walking node_modules folders.
141
+ function buildSassLoadPaths(filePath) {
142
+ const loadPaths = new Set();
143
+ let cursor = node_path_1.default.dirname(filePath);
144
+ const root = node_path_1.default.parse(cursor).root;
145
+ while (true) {
146
+ loadPaths.add(cursor);
147
+ loadPaths.add(node_path_1.default.join(cursor, 'node_modules'));
148
+ if (cursor === root)
149
+ break;
150
+ cursor = node_path_1.default.dirname(cursor);
151
+ }
152
+ const cwd = process.cwd();
153
+ loadPaths.add(cwd);
154
+ loadPaths.add(node_path_1.default.join(cwd, 'node_modules'));
155
+ return Array.from(loadPaths).filter(dir => dir && (0, node_fs_1.existsSync)(dir));
156
+ }
157
+ function createSassImporter({ cwd, resolver }) {
158
+ if (!resolver)
159
+ return undefined;
160
+ const debug = process.env.KNIGHTED_CSS_DEBUG_SASS === '1';
161
+ return {
162
+ async canonicalize(url, context) {
163
+ if (debug) {
164
+ console.error('[knighted-css:sass] canonicalize request:', url);
165
+ if (context?.containingUrl) {
166
+ console.error('[knighted-css:sass] containing url:', context.containingUrl.href);
167
+ }
168
+ }
169
+ if (shouldNormalizeSpecifier(url)) {
170
+ const resolvedPath = await resolveAliasSpecifier(url, resolver, cwd);
171
+ if (!resolvedPath) {
172
+ if (debug) {
173
+ console.error('[knighted-css:sass] resolver returned no result for', url);
174
+ }
175
+ return null;
176
+ }
177
+ const fileUrl = (0, node_url_1.pathToFileURL)(resolvedPath);
178
+ if (debug) {
179
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
180
+ }
181
+ return fileUrl;
182
+ }
183
+ const relativePath = resolveRelativeSpecifier(url, context?.containingUrl);
184
+ if (relativePath) {
185
+ const fileUrl = (0, node_url_1.pathToFileURL)(relativePath);
186
+ if (debug) {
187
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
188
+ }
189
+ return fileUrl;
190
+ }
191
+ return null;
192
+ },
193
+ async load(canonicalUrl) {
194
+ if (debug) {
195
+ console.error('[knighted-css:sass] load request:', canonicalUrl.href);
196
+ }
197
+ const filePath = (0, node_url_1.fileURLToPath)(canonicalUrl);
198
+ const contents = await node_fs_1.promises.readFile(filePath, 'utf8');
199
+ return {
200
+ contents,
201
+ syntax: inferSassSyntax(filePath),
202
+ };
203
+ },
204
+ };
205
+ }
206
+ async function resolveAliasSpecifier(specifier, resolver, cwd) {
207
+ const resolved = await resolver(specifier, { cwd });
208
+ if (!resolved) {
209
+ return undefined;
210
+ }
211
+ if (resolved.startsWith('file://')) {
212
+ return ensureSassPath((0, node_url_1.fileURLToPath)(new URL(resolved)));
213
+ }
214
+ const normalized = node_path_1.default.isAbsolute(resolved) ? resolved : node_path_1.default.resolve(cwd, resolved);
215
+ return ensureSassPath(normalized);
216
+ }
217
+ function shouldNormalizeSpecifier(specifier) {
218
+ const schemeMatch = specifier.match(/^([a-z][\w+.-]*):/i);
219
+ if (!schemeMatch) {
220
+ return false;
221
+ }
222
+ const scheme = schemeMatch[1].toLowerCase();
223
+ if (scheme === 'file' ||
224
+ scheme === 'http' ||
225
+ scheme === 'https' ||
226
+ scheme === 'data' ||
227
+ scheme === 'sass') {
228
+ return false;
229
+ }
230
+ return true;
231
+ }
232
+ function inferSassSyntax(filePath) {
233
+ return filePath.endsWith('.sass') ? 'indented' : 'scss';
234
+ }
235
+ function ensureSassPath(filePath) {
236
+ if ((0, node_fs_1.existsSync)(filePath)) {
237
+ return filePath;
238
+ }
239
+ const ext = node_path_1.default.extname(filePath);
240
+ const dir = node_path_1.default.dirname(filePath);
241
+ const base = node_path_1.default.basename(filePath, ext);
242
+ const partialCandidate = node_path_1.default.join(dir, `_${base}${ext}`);
243
+ if (ext && (0, node_fs_1.existsSync)(partialCandidate)) {
244
+ return partialCandidate;
245
+ }
246
+ const indexCandidate = node_path_1.default.join(dir, base, `index${ext}`);
247
+ if (ext && (0, node_fs_1.existsSync)(indexCandidate)) {
248
+ return indexCandidate;
249
+ }
250
+ const partialIndexCandidate = node_path_1.default.join(dir, base, `_index${ext}`);
251
+ if (ext && (0, node_fs_1.existsSync)(partialIndexCandidate)) {
252
+ return partialIndexCandidate;
253
+ }
254
+ return undefined;
255
+ }
256
+ function resolveRelativeSpecifier(specifier, containingUrl) {
257
+ if (!containingUrl || containingUrl.protocol !== 'file:') {
258
+ return undefined;
259
+ }
260
+ if (/^[a-z][\w+.-]*:/i.test(specifier)) {
261
+ return undefined;
262
+ }
263
+ const containingPath = (0, node_url_1.fileURLToPath)(containingUrl);
264
+ const baseDir = node_path_1.default.dirname(containingPath);
265
+ const candidate = node_path_1.default.resolve(baseDir, specifier);
266
+ return ensureSassPath(candidate);
267
+ }
131
268
  async function compileLess(filePath, peerResolver) {
132
269
  const mod = await optionalPeer('less', 'Less', peerResolver);
133
270
  const less = unwrapModuleNamespace(mod);
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stableClassFromModule = void 0;
4
+ exports.stableToken = stableToken;
5
+ exports.stableClass = stableClass;
6
+ exports.stableSelector = stableSelector;
7
+ exports.createStableClassFactory = createStableClassFactory;
8
+ exports.stableClassName = stableClassName;
9
+ const DEFAULT_NAMESPACE = 'knighted';
10
+ const defaultJoin = (values) => values.filter(Boolean).join(' ');
11
+ const normalizeToken = (token) => {
12
+ const sanitized = token
13
+ .trim()
14
+ .replace(/\s+/g, '-')
15
+ .replace(/[^A-Za-z0-9_-]/g, '-')
16
+ .replace(/-+/g, '-')
17
+ .replace(/^-|-$/g, '');
18
+ return sanitized.length ? sanitized : 'stable';
19
+ };
20
+ function stableToken(token, options) {
21
+ const normalized = normalizeToken(token);
22
+ const namespace = options?.namespace?.trim() ?? DEFAULT_NAMESPACE;
23
+ if (!namespace) {
24
+ return normalized;
25
+ }
26
+ return `${namespace}-${normalized}`;
27
+ }
28
+ function stableClass(token, options) {
29
+ return stableToken(token, options);
30
+ }
31
+ function stableSelector(token, options) {
32
+ return `.${stableToken(token, options)}`;
33
+ }
34
+ function createStableClassFactory(options) {
35
+ return (token) => stableClass(token, options);
36
+ }
37
+ function stableClassName(styles, key, options) {
38
+ const hashed = styles[key] ?? '';
39
+ const token = options?.token ?? String(key);
40
+ const stable = stableClass(token, options);
41
+ const join = options?.join ?? defaultJoin;
42
+ return join([hashed, stable]);
43
+ }
44
+ exports.stableClassFromModule = stableClassName;
@@ -0,0 +1,13 @@
1
+ export interface StableSelectorOptions {
2
+ namespace?: string;
3
+ }
4
+ export interface StableClassNameOptions extends StableSelectorOptions {
5
+ token?: string;
6
+ join?: (values: string[]) => string;
7
+ }
8
+ export declare function stableToken(token: string, options?: StableSelectorOptions): string;
9
+ export declare function stableClass(token: string, options?: StableSelectorOptions): string;
10
+ export declare function stableSelector(token: string, options?: StableSelectorOptions): string;
11
+ export declare function createStableClassFactory(options?: StableSelectorOptions): (token: string) => string;
12
+ export declare function stableClassName<T extends Record<string, string>>(styles: T, key: keyof T | string, options?: StableClassNameOptions): string;
13
+ export declare const stableClassFromModule: typeof stableClassName;
package/dist/css.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
- import { promises as fs } from 'node:fs';
2
+ import { existsSync, promises as fs } from 'node:fs';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
4
  import dependencyTree from 'dependency-tree';
4
5
  import { composeVisitors, transform as lightningTransform, } from 'lightningcss';
5
6
  import { applyStringSpecificityBoost, buildSpecificityVisitor, } from './helpers.js';
@@ -26,6 +27,7 @@ export async function cssWithMeta(entry, options = {}) {
26
27
  const chunk = await compileStyleModule(file, {
27
28
  cwd,
28
29
  peerResolver: options.peerResolver,
30
+ resolver: options.resolver,
29
31
  });
30
32
  if (chunk) {
31
33
  chunks.push(chunk);
@@ -96,13 +98,17 @@ function matchExtension(filePath, extensions) {
96
98
  const lower = filePath.toLowerCase();
97
99
  return extensions.find(ext => lower.endsWith(ext));
98
100
  }
99
- async function compileStyleModule(file, { cwd, peerResolver }) {
101
+ async function compileStyleModule(file, { cwd, peerResolver, resolver, }) {
100
102
  switch (file.ext) {
101
103
  case '.css':
102
104
  return fs.readFile(file.path, 'utf8');
103
105
  case '.scss':
104
106
  case '.sass':
105
- return compileSass(file.path, file.ext === '.sass', peerResolver);
107
+ return compileSass(file.path, file.ext === '.sass', {
108
+ cwd,
109
+ peerResolver,
110
+ resolver,
111
+ });
106
112
  case '.less':
107
113
  return compileLess(file.path, peerResolver);
108
114
  case '.css.ts':
@@ -111,14 +117,145 @@ async function compileStyleModule(file, { cwd, peerResolver }) {
111
117
  return '';
112
118
  }
113
119
  }
114
- async function compileSass(filePath, indented, peerResolver) {
120
+ async function compileSass(filePath, indented, { cwd, peerResolver, resolver, }) {
115
121
  const sassModule = await optionalPeer('sass', 'Sass', peerResolver);
116
122
  const sass = sassModule;
117
- const result = sass.compile(filePath, {
123
+ const importer = createSassImporter({ cwd, resolver });
124
+ const result = await sass.compileAsync(filePath, {
118
125
  style: 'expanded',
126
+ loadPaths: buildSassLoadPaths(filePath),
127
+ importers: importer ? [importer] : undefined,
119
128
  });
120
129
  return result.css;
121
130
  }
131
+ // Ensure Sass can resolve bare module specifiers by walking node_modules folders.
132
+ function buildSassLoadPaths(filePath) {
133
+ const loadPaths = new Set();
134
+ let cursor = path.dirname(filePath);
135
+ const root = path.parse(cursor).root;
136
+ while (true) {
137
+ loadPaths.add(cursor);
138
+ loadPaths.add(path.join(cursor, 'node_modules'));
139
+ if (cursor === root)
140
+ break;
141
+ cursor = path.dirname(cursor);
142
+ }
143
+ const cwd = process.cwd();
144
+ loadPaths.add(cwd);
145
+ loadPaths.add(path.join(cwd, 'node_modules'));
146
+ return Array.from(loadPaths).filter(dir => dir && existsSync(dir));
147
+ }
148
+ function createSassImporter({ cwd, resolver }) {
149
+ if (!resolver)
150
+ return undefined;
151
+ const debug = process.env.KNIGHTED_CSS_DEBUG_SASS === '1';
152
+ return {
153
+ async canonicalize(url, context) {
154
+ if (debug) {
155
+ console.error('[knighted-css:sass] canonicalize request:', url);
156
+ if (context?.containingUrl) {
157
+ console.error('[knighted-css:sass] containing url:', context.containingUrl.href);
158
+ }
159
+ }
160
+ if (shouldNormalizeSpecifier(url)) {
161
+ const resolvedPath = await resolveAliasSpecifier(url, resolver, cwd);
162
+ if (!resolvedPath) {
163
+ if (debug) {
164
+ console.error('[knighted-css:sass] resolver returned no result for', url);
165
+ }
166
+ return null;
167
+ }
168
+ const fileUrl = pathToFileURL(resolvedPath);
169
+ if (debug) {
170
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
171
+ }
172
+ return fileUrl;
173
+ }
174
+ const relativePath = resolveRelativeSpecifier(url, context?.containingUrl);
175
+ if (relativePath) {
176
+ const fileUrl = pathToFileURL(relativePath);
177
+ if (debug) {
178
+ console.error('[knighted-css:sass] canonical url:', fileUrl.href);
179
+ }
180
+ return fileUrl;
181
+ }
182
+ return null;
183
+ },
184
+ async load(canonicalUrl) {
185
+ if (debug) {
186
+ console.error('[knighted-css:sass] load request:', canonicalUrl.href);
187
+ }
188
+ const filePath = fileURLToPath(canonicalUrl);
189
+ const contents = await fs.readFile(filePath, 'utf8');
190
+ return {
191
+ contents,
192
+ syntax: inferSassSyntax(filePath),
193
+ };
194
+ },
195
+ };
196
+ }
197
+ async function resolveAliasSpecifier(specifier, resolver, cwd) {
198
+ const resolved = await resolver(specifier, { cwd });
199
+ if (!resolved) {
200
+ return undefined;
201
+ }
202
+ if (resolved.startsWith('file://')) {
203
+ return ensureSassPath(fileURLToPath(new URL(resolved)));
204
+ }
205
+ const normalized = path.isAbsolute(resolved) ? resolved : path.resolve(cwd, resolved);
206
+ return ensureSassPath(normalized);
207
+ }
208
+ function shouldNormalizeSpecifier(specifier) {
209
+ const schemeMatch = specifier.match(/^([a-z][\w+.-]*):/i);
210
+ if (!schemeMatch) {
211
+ return false;
212
+ }
213
+ const scheme = schemeMatch[1].toLowerCase();
214
+ if (scheme === 'file' ||
215
+ scheme === 'http' ||
216
+ scheme === 'https' ||
217
+ scheme === 'data' ||
218
+ scheme === 'sass') {
219
+ return false;
220
+ }
221
+ return true;
222
+ }
223
+ function inferSassSyntax(filePath) {
224
+ return filePath.endsWith('.sass') ? 'indented' : 'scss';
225
+ }
226
+ function ensureSassPath(filePath) {
227
+ if (existsSync(filePath)) {
228
+ return filePath;
229
+ }
230
+ const ext = path.extname(filePath);
231
+ const dir = path.dirname(filePath);
232
+ const base = path.basename(filePath, ext);
233
+ const partialCandidate = path.join(dir, `_${base}${ext}`);
234
+ if (ext && existsSync(partialCandidate)) {
235
+ return partialCandidate;
236
+ }
237
+ const indexCandidate = path.join(dir, base, `index${ext}`);
238
+ if (ext && existsSync(indexCandidate)) {
239
+ return indexCandidate;
240
+ }
241
+ const partialIndexCandidate = path.join(dir, base, `_index${ext}`);
242
+ if (ext && existsSync(partialIndexCandidate)) {
243
+ return partialIndexCandidate;
244
+ }
245
+ return undefined;
246
+ }
247
+ function resolveRelativeSpecifier(specifier, containingUrl) {
248
+ if (!containingUrl || containingUrl.protocol !== 'file:') {
249
+ return undefined;
250
+ }
251
+ if (/^[a-z][\w+.-]*:/i.test(specifier)) {
252
+ return undefined;
253
+ }
254
+ const containingPath = fileURLToPath(containingUrl);
255
+ const baseDir = path.dirname(containingPath);
256
+ const candidate = path.resolve(baseDir, specifier);
257
+ return ensureSassPath(candidate);
258
+ }
122
259
  async function compileLess(filePath, peerResolver) {
123
260
  const mod = await optionalPeer('less', 'Less', peerResolver);
124
261
  const less = unwrapModuleNamespace(mod);
@@ -0,0 +1,13 @@
1
+ export interface StableSelectorOptions {
2
+ namespace?: string;
3
+ }
4
+ export interface StableClassNameOptions extends StableSelectorOptions {
5
+ token?: string;
6
+ join?: (values: string[]) => string;
7
+ }
8
+ export declare function stableToken(token: string, options?: StableSelectorOptions): string;
9
+ export declare function stableClass(token: string, options?: StableSelectorOptions): string;
10
+ export declare function stableSelector(token: string, options?: StableSelectorOptions): string;
11
+ export declare function createStableClassFactory(options?: StableSelectorOptions): (token: string) => string;
12
+ export declare function stableClassName<T extends Record<string, string>>(styles: T, key: keyof T | string, options?: StableClassNameOptions): string;
13
+ export declare const stableClassFromModule: typeof stableClassName;
@@ -0,0 +1,36 @@
1
+ const DEFAULT_NAMESPACE = 'knighted';
2
+ const defaultJoin = (values) => values.filter(Boolean).join(' ');
3
+ const normalizeToken = (token) => {
4
+ const sanitized = token
5
+ .trim()
6
+ .replace(/\s+/g, '-')
7
+ .replace(/[^A-Za-z0-9_-]/g, '-')
8
+ .replace(/-+/g, '-')
9
+ .replace(/^-|-$/g, '');
10
+ return sanitized.length ? sanitized : 'stable';
11
+ };
12
+ export function stableToken(token, options) {
13
+ const normalized = normalizeToken(token);
14
+ const namespace = options?.namespace?.trim() ?? DEFAULT_NAMESPACE;
15
+ if (!namespace) {
16
+ return normalized;
17
+ }
18
+ return `${namespace}-${normalized}`;
19
+ }
20
+ export function stableClass(token, options) {
21
+ return stableToken(token, options);
22
+ }
23
+ export function stableSelector(token, options) {
24
+ return `.${stableToken(token, options)}`;
25
+ }
26
+ export function createStableClassFactory(options) {
27
+ return (token) => stableClass(token, options);
28
+ }
29
+ export function stableClassName(styles, key, options) {
30
+ const hashed = styles[key] ?? '';
31
+ const token = options?.token ?? String(key);
32
+ const stable = stableClass(token, options);
33
+ const join = options?.join ?? defaultJoin;
34
+ return join([hashed, stable]);
35
+ }
36
+ export const stableClassFromModule = stableClassName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/css",
3
- "version": "1.0.0-rc.3",
3
+ "version": "1.0.0-rc.5",
4
4
  "description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.",
5
5
  "type": "module",
6
6
  "main": "./dist/css.js",
@@ -32,6 +32,18 @@
32
32
  "./loader-queries": {
33
33
  "types": "./loader-queries.d.ts",
34
34
  "default": "./loader-queries.d.ts"
35
+ },
36
+ "./stableSelectors": {
37
+ "types": "./dist/stableSelectors.d.ts",
38
+ "import": "./dist/stableSelectors.js",
39
+ "require": "./dist/cjs/stableSelectors.cjs"
40
+ },
41
+ "./stable": {
42
+ "sass": "./stable/_index.scss",
43
+ "default": "./stable/_index.scss"
44
+ },
45
+ "./stable/stable.css": {
46
+ "default": "./stable/stable.css"
35
47
  }
36
48
  },
37
49
  "keywords": [
@@ -80,7 +92,8 @@
80
92
  "files": [
81
93
  "dist",
82
94
  "loader-queries.d.ts",
83
- "types.d.ts"
95
+ "types.d.ts",
96
+ "stable"
84
97
  ],
85
98
  "author": "KCM <knightedcodemonkey@gmail.com>",
86
99
  "license": "MIT",
@@ -0,0 +1,57 @@
1
+ // Knighted stable selector mixins
2
+ // Usage: @use '@knighted/css/stable' as knighted;
3
+ // .button { @include knighted.stable('button') { ... } }
4
+
5
+ @use 'sass:meta';
6
+
7
+ $knighted-stable-namespace: 'knighted' !default;
8
+
9
+ @function _knighted-normalize-token($token) {
10
+ @if meta.type-of($token) == 'number' {
11
+ @return $token;
12
+ }
13
+ @return $token;
14
+ }
15
+
16
+ @function stable-token($token, $namespace: $knighted-stable-namespace) {
17
+ $normalized-token: _knighted-normalize-token($token);
18
+ @if $namespace == '' {
19
+ @return $normalized-token;
20
+ }
21
+ @return '#{$namespace}-#{$normalized-token}';
22
+ }
23
+
24
+ @function stable-class($token, $namespace: $knighted-stable-namespace) {
25
+ $token-value: stable-token($token, $namespace);
26
+ @return '.#{$token-value}';
27
+ }
28
+
29
+ @mixin stable($token, $namespace: $knighted-stable-namespace) {
30
+ @if not & {
31
+ @error 'The knighted.stable mixin must be used within a selector context so that "&" is defined.';
32
+ }
33
+
34
+ $stable-selector: stable-class($token, $namespace);
35
+ @at-root #{&},
36
+ #{$stable-selector} {
37
+ @content;
38
+ }
39
+ }
40
+
41
+ @mixin stable-at-root($selector, $token, $namespace: $knighted-stable-namespace) {
42
+ $stable-selector: stable-class($token, $namespace);
43
+ @at-root #{$selector},
44
+ #{$stable-selector} {
45
+ @content;
46
+ }
47
+ }
48
+
49
+ @function stable-class-name($token, $namespace: $knighted-stable-namespace) {
50
+ @return stable-token($token, $namespace);
51
+ }
52
+
53
+ @mixin stable-only($token, $namespace: $knighted-stable-namespace) {
54
+ @at-root #{stable-class($token, $namespace)} {
55
+ @content;
56
+ }
57
+ }
@@ -0,0 +1,15 @@
1
+ @layer knighted.stable;
2
+
3
+ :root {
4
+ --knighted-stable-namespace: 'knighted';
5
+ }
6
+
7
+ /*
8
+ Usage:
9
+ @import '@knighted/css/stable/stable.css';
10
+ @layer knighted.stable {
11
+ .knighted-button {
12
+ declarations go here;
13
+ }
14
+ }
15
+ */