ruscus 0.0.0-stage → 0.0.2
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/LICENSE +21 -0
- package/README.md +361 -2
- package/dist/index.d.mts +1351 -0
- package/dist/index.mjs +661 -0
- package/dist/vite.d.mts +20 -0
- package/dist/vite.mjs +0 -0
- package/package.json +74 -4
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
//#region src/types/ids.ts
|
|
2
|
+
const ELEMENT_ROOT = "root";
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/projection/elements.ts
|
|
5
|
+
/**
|
|
6
|
+
* Project a root node and its children
|
|
7
|
+
*/
|
|
8
|
+
function projectTree(namespace, elements, { format, hasVariants, name }) {
|
|
9
|
+
return {
|
|
10
|
+
...projectElements(namespace, elements, {
|
|
11
|
+
format,
|
|
12
|
+
name
|
|
13
|
+
}),
|
|
14
|
+
[ELEMENT_ROOT]: projectNode(name.forClass(namespace), {
|
|
15
|
+
format,
|
|
16
|
+
hasVariants,
|
|
17
|
+
name
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Project elements under a namespace
|
|
23
|
+
*/
|
|
24
|
+
function projectElements(namespace, elements, { format, name }) {
|
|
25
|
+
return Object.entries(elements).reduce((previous, [key, element]) => {
|
|
26
|
+
if (element) previous[key] = projectElement([...namespace, key], element, {
|
|
27
|
+
format,
|
|
28
|
+
name
|
|
29
|
+
});
|
|
30
|
+
return previous;
|
|
31
|
+
}, {});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Project an element as a class name, selector, or child tree
|
|
35
|
+
*/
|
|
36
|
+
function projectElement(path, element, options) {
|
|
37
|
+
const { children } = element;
|
|
38
|
+
const hasVariants = element.variants !== void 0;
|
|
39
|
+
return Object.keys(children).length ? projectTree(path, children, {
|
|
40
|
+
...options,
|
|
41
|
+
hasVariants
|
|
42
|
+
}) : projectNode(options.name.forClass(path), {
|
|
43
|
+
...options,
|
|
44
|
+
hasVariants
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Project a node as a class name or variant projector
|
|
49
|
+
*/
|
|
50
|
+
function projectNode(className, { format, hasVariants, name }) {
|
|
51
|
+
const root = formatClass(className, format);
|
|
52
|
+
return hasVariants ? createVariantProjector(root, format, name) : root;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Format a class name
|
|
56
|
+
*/
|
|
57
|
+
function formatClass(name, format) {
|
|
58
|
+
return format === "selectors" ? `.${name}` : name;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a variant projector anchored to a compile-time root
|
|
62
|
+
*/
|
|
63
|
+
function createVariantProjector(anchor, format, name) {
|
|
64
|
+
const joiner = format === "selectors" ? "" : " ";
|
|
65
|
+
return (...vs) => {
|
|
66
|
+
return [anchor, ...vs.filter(Boolean).map((v) => formatClass(name.forVariant(v), format))].join(joiner);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/styles.ts
|
|
71
|
+
/**
|
|
72
|
+
* Convert a camel-cased value to kebab case
|
|
73
|
+
*/
|
|
74
|
+
function camelToKebab(camel) {
|
|
75
|
+
return camel.replace(/[A-Z]/g, (l) => `-${l.toLowerCase()}`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Merge multiple styles on top of a base
|
|
79
|
+
*/
|
|
80
|
+
function mergeStyles(base, ...others) {
|
|
81
|
+
const merged = { ...base };
|
|
82
|
+
for (const other of others) Object.assign(merged, other);
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
var PropertyProjector = class {
|
|
86
|
+
names;
|
|
87
|
+
/**
|
|
88
|
+
* Create a mapper of camel-cased values to CSS properties
|
|
89
|
+
*/
|
|
90
|
+
constructor() {
|
|
91
|
+
this.names = /* @__PURE__ */ new Map();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Project a camel-cased value as a CSS property
|
|
95
|
+
*/
|
|
96
|
+
project(camel) {
|
|
97
|
+
let prop = this.names.get(camel);
|
|
98
|
+
if (prop === void 0) {
|
|
99
|
+
prop = camelToKebab(camel);
|
|
100
|
+
this.names.set(camel, prop);
|
|
101
|
+
}
|
|
102
|
+
return prop;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/projection/identifiers.ts
|
|
107
|
+
/**
|
|
108
|
+
* Project a component's identifiers as a map of labels to values
|
|
109
|
+
*/
|
|
110
|
+
function projectIdentifiers(component) {
|
|
111
|
+
return Object.fromEntries(Object.entries(component.identifiers).map(([key, type]) => {
|
|
112
|
+
const scoped = `${component.id}--${camelToKebab(key)}`;
|
|
113
|
+
return [key, type === "variable" ? `--${scoped}` : scoped];
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/contexts.ts
|
|
118
|
+
/**
|
|
119
|
+
* Build a context for derived component styles
|
|
120
|
+
*/
|
|
121
|
+
function buildComponentContext(component, { name }) {
|
|
122
|
+
return {
|
|
123
|
+
els: projectTree([component.id], component.elements, {
|
|
124
|
+
format: "selectors",
|
|
125
|
+
hasVariants: component.variants !== void 0,
|
|
126
|
+
name
|
|
127
|
+
}),
|
|
128
|
+
ids: projectIdentifiers(component),
|
|
129
|
+
variants: createVariantProjector("&", "selectors", name)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Build a context for derived element styles
|
|
134
|
+
*/
|
|
135
|
+
function buildElementContext(namespace, element, { name }) {
|
|
136
|
+
return {
|
|
137
|
+
els: projectTree(namespace, element.children, {
|
|
138
|
+
format: "selectors",
|
|
139
|
+
hasVariants: element.variants !== void 0,
|
|
140
|
+
name
|
|
141
|
+
}),
|
|
142
|
+
variants: createVariantProjector("&", "selectors", name)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/fragments.ts
|
|
147
|
+
/**
|
|
148
|
+
* Create a fragment factory
|
|
149
|
+
*/
|
|
150
|
+
function createFactory(scope) {
|
|
151
|
+
const factory = ((styles = {}) => ({
|
|
152
|
+
nested: (definitions) => {
|
|
153
|
+
const nested = factory(styles);
|
|
154
|
+
for (const fragment of Object.values(definitions)) fragment.scope = "relative";
|
|
155
|
+
nested.styles.nested = { ...definitions };
|
|
156
|
+
return nested;
|
|
157
|
+
},
|
|
158
|
+
scope,
|
|
159
|
+
styles: {
|
|
160
|
+
nested: {},
|
|
161
|
+
root: styles
|
|
162
|
+
}
|
|
163
|
+
}));
|
|
164
|
+
factory.rules = (definitions) => factory().nested(definitions);
|
|
165
|
+
return factory;
|
|
166
|
+
}
|
|
167
|
+
const defineAbsoluteFragment = createFactory("absolute");
|
|
168
|
+
const defineRelativeFragment = createFactory("relative");
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/naming/shared.ts
|
|
171
|
+
var Naming = class {
|
|
172
|
+
component;
|
|
173
|
+
/**
|
|
174
|
+
* Create a naming strategy for a component
|
|
175
|
+
*/
|
|
176
|
+
constructor(component) {
|
|
177
|
+
this.component = component;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Produce the class name for a variant
|
|
181
|
+
*/
|
|
182
|
+
forVariant(name) {
|
|
183
|
+
return `is-${name}`;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/naming/compact.ts
|
|
188
|
+
const SEPARATOR = "\0";
|
|
189
|
+
var CompactNaming = class extends Naming {
|
|
190
|
+
prefix;
|
|
191
|
+
suffixes;
|
|
192
|
+
/**
|
|
193
|
+
* Build compact suffixes for all element paths
|
|
194
|
+
*/
|
|
195
|
+
constructor(component) {
|
|
196
|
+
super(component);
|
|
197
|
+
this.prefix = `r${hash(component.id)}`;
|
|
198
|
+
this.suffixes = this.buildSuffixes(component.elements);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Produce a compact class name
|
|
202
|
+
*/
|
|
203
|
+
forClass(levels) {
|
|
204
|
+
if (levels.length <= 1) return this.prefix;
|
|
205
|
+
const suffix = this.suffixes.get(levels.slice(1).join(SEPARATOR));
|
|
206
|
+
if (suffix === void 0) throw new Error(`Unknown element path for compact naming: ${levels.join(" > ")}`);
|
|
207
|
+
return `${this.prefix}_${suffix}`;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Map each element path to a compact suffix
|
|
211
|
+
*/
|
|
212
|
+
buildSuffixes(elements) {
|
|
213
|
+
const paths = this.collectElementPaths(elements).sort((alfa, bravo) => alfa.localeCompare(bravo));
|
|
214
|
+
const suffixes = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const [index, path] of paths.entries()) suffixes.set(path, index.toString(36));
|
|
216
|
+
return suffixes;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Collect element paths in depth-first order
|
|
220
|
+
*/
|
|
221
|
+
collectElementPaths(elements, base = "") {
|
|
222
|
+
const paths = [];
|
|
223
|
+
for (const name of Object.keys(elements).sort()) {
|
|
224
|
+
const element = elements[name];
|
|
225
|
+
if (element === void 0) continue;
|
|
226
|
+
const path = base.length === 0 ? name : `${base}${SEPARATOR}${name}`;
|
|
227
|
+
paths.push(path);
|
|
228
|
+
paths.push(...this.collectElementPaths(element.children, path));
|
|
229
|
+
}
|
|
230
|
+
return paths;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Build a short hash for a value using a 32-bit FNV-1a algorithm
|
|
235
|
+
*/
|
|
236
|
+
function hash(value, length = 6) {
|
|
237
|
+
let hash = 2166136261;
|
|
238
|
+
for (const character of value) {
|
|
239
|
+
hash ^= character.charCodeAt(0);
|
|
240
|
+
hash = Math.imul(hash, 16777619);
|
|
241
|
+
}
|
|
242
|
+
return (hash >>> 0).toString(36).slice(0, length).padStart(length, "0");
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/naming/verbose.ts
|
|
246
|
+
var VerboseNaming = class extends Naming {
|
|
247
|
+
/**
|
|
248
|
+
* Produce a BEM-style class name
|
|
249
|
+
*/
|
|
250
|
+
forClass(levels) {
|
|
251
|
+
return levels.join("__");
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/naming.ts
|
|
256
|
+
/**
|
|
257
|
+
* Create a name generator for a component
|
|
258
|
+
*/
|
|
259
|
+
function useNaming(component, scheme = "verbose") {
|
|
260
|
+
return scheme === "compact" ? new CompactNaming(component) : new VerboseNaming(component);
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/compilation.ts
|
|
264
|
+
/**
|
|
265
|
+
* Generate CSS for a component's styles
|
|
266
|
+
*/
|
|
267
|
+
function styleComponent(component, config = {}) {
|
|
268
|
+
return new ComponentCompiler(component, config).compile();
|
|
269
|
+
}
|
|
270
|
+
var ComponentCompiler = class {
|
|
271
|
+
blocks;
|
|
272
|
+
component;
|
|
273
|
+
config;
|
|
274
|
+
indent;
|
|
275
|
+
name;
|
|
276
|
+
/**
|
|
277
|
+
* Create a compiler for a component's styles
|
|
278
|
+
*/
|
|
279
|
+
constructor(component, config) {
|
|
280
|
+
this.config = {
|
|
281
|
+
indentation: 2,
|
|
282
|
+
naming: "verbose",
|
|
283
|
+
properties: new PropertyProjector(),
|
|
284
|
+
...config
|
|
285
|
+
};
|
|
286
|
+
this.blocks = [];
|
|
287
|
+
this.component = component;
|
|
288
|
+
this.indent = " ".repeat(this.config.indentation);
|
|
289
|
+
this.name = useNaming(component, config.naming);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Produce CSS for the component's styles
|
|
293
|
+
*/
|
|
294
|
+
compile() {
|
|
295
|
+
const { component, name } = this;
|
|
296
|
+
const { elements, variants } = component;
|
|
297
|
+
const root = this.buildSelector();
|
|
298
|
+
const context = buildComponentContext(component, { name });
|
|
299
|
+
this.blocks.push(this.formatRule(root, component.styles));
|
|
300
|
+
if (variants) this.addVariantRules(root, variants(defineRelativeFragment, context));
|
|
301
|
+
for (const [name, element] of Object.entries(elements)) this.addElementStyles([name], element);
|
|
302
|
+
for (const provider of component.nested) this.addRelativeFragmentRules(root, provider(defineRelativeFragment, context));
|
|
303
|
+
for (const provider of component.global) this.addGlobalFragmentRules(provider(defineAbsoluteFragment, context));
|
|
304
|
+
return this.blocks.filter(Boolean).join("\n\n");
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Add base styles for a component's elements
|
|
308
|
+
*/
|
|
309
|
+
addElementStyles(path, element) {
|
|
310
|
+
const { children, variants } = element;
|
|
311
|
+
const selector = this.buildSelector(...path);
|
|
312
|
+
const context = buildElementContext([this.component.id, ...path], element, { name: this.name });
|
|
313
|
+
this.blocks.push(this.formatRule(selector, element.styles));
|
|
314
|
+
if (variants) this.addVariantRules(selector, variants(defineRelativeFragment, context));
|
|
315
|
+
for (const provider of element.nested) this.addRelativeFragmentRules(selector, provider(defineRelativeFragment, context));
|
|
316
|
+
for (const [name, child] of Object.entries(children)) this.addElementStyles([...path, name], child);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Add variant rules for component or element variants
|
|
320
|
+
*/
|
|
321
|
+
addVariantRules(anchor, definitions) {
|
|
322
|
+
for (const [name, fragment] of Object.entries(definitions)) this.blocks.push(this.formatFragmentRules(`${anchor}.${this.name.forVariant(name)}`, fragment));
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Add global fragment rules
|
|
326
|
+
*/
|
|
327
|
+
addGlobalFragmentRules(definitions) {
|
|
328
|
+
for (const [key, fragment] of Object.entries(definitions)) this.blocks.push(this.formatFragmentRules(key, fragment));
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Add relative fragment rules
|
|
332
|
+
*/
|
|
333
|
+
addRelativeFragmentRules(anchor, definitions) {
|
|
334
|
+
for (const [key, fragment] of Object.entries(definitions)) this.blocks.push(this.formatFragmentRules(this.buildNestedSelector(anchor, key), fragment));
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Format a fragment as CSS at a selector
|
|
338
|
+
*/
|
|
339
|
+
formatFragmentRules(parentSelector, fragment) {
|
|
340
|
+
const { nested, root: styles } = fragment.styles;
|
|
341
|
+
const nestedEntries = Object.entries(nested);
|
|
342
|
+
if (nestedEntries.length === 0) return this.formatRule(parentSelector, styles);
|
|
343
|
+
const inner = [];
|
|
344
|
+
const declarations = this.formatDeclarations(styles);
|
|
345
|
+
if (declarations) inner.push(declarations);
|
|
346
|
+
for (const [key, child] of nestedEntries) {
|
|
347
|
+
const rule = this.formatFragmentRules(key.startsWith("&") ? `${parentSelector}${key.slice(1)}` : key, child);
|
|
348
|
+
if (rule) inner.push(rule);
|
|
349
|
+
}
|
|
350
|
+
if (!inner.length) return null;
|
|
351
|
+
return this.formatNestedBlock(parentSelector, inner);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Format declaration as key/value pairs
|
|
355
|
+
*/
|
|
356
|
+
formatDeclarations(styles) {
|
|
357
|
+
const { config: { properties }, indent } = this;
|
|
358
|
+
const props = Object.entries(styles).filter(([_, v]) => v !== void 0);
|
|
359
|
+
if (props.length === 0) return null;
|
|
360
|
+
return props.map(([p]) => `${indent}${properties.project(p)}: ${styles[p]};`).join("\n");
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Wrap nested rules in a selector block
|
|
364
|
+
*/
|
|
365
|
+
formatNestedBlock(selector, inner) {
|
|
366
|
+
const { indent } = this;
|
|
367
|
+
return `${selector} {\n${inner.map((rule) => rule.split("\n").map((line) => `${indent}${line}`).join("\n")).join("\n\n")}\n}`;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Build a selector for a nested rule key
|
|
371
|
+
*/
|
|
372
|
+
buildNestedSelector(anchor, key) {
|
|
373
|
+
if (key.startsWith("&")) return `${anchor}${key.slice(1)}`;
|
|
374
|
+
else if (key === anchor) return anchor;
|
|
375
|
+
else return `${anchor} ${key}`;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Build a selector for a component element
|
|
379
|
+
*/
|
|
380
|
+
buildSelector(...parts) {
|
|
381
|
+
return `.${this.name.forClass([this.component.id, ...parts])}`;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Format a CSS rule
|
|
385
|
+
*/
|
|
386
|
+
formatRule(selector, styles) {
|
|
387
|
+
const body = this.formatDeclarations(styles);
|
|
388
|
+
return body === null ? null : `${selector} {\n${body}\n}`;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/builder.ts
|
|
393
|
+
var Builder = class {
|
|
394
|
+
current;
|
|
395
|
+
/**
|
|
396
|
+
* Create a builder that progressively updates its state
|
|
397
|
+
*/
|
|
398
|
+
constructor(defaultState) {
|
|
399
|
+
this.current = defaultState;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Set a state field
|
|
403
|
+
*
|
|
404
|
+
* @throws if the field already has a value
|
|
405
|
+
*/
|
|
406
|
+
setStateField(key, value) {
|
|
407
|
+
const current = this.current[key];
|
|
408
|
+
if (current !== void 0) {
|
|
409
|
+
if (typeof current === "object" ? Object.keys(current).length > 0 : true) throw new Error(`${key} may only be set once`);
|
|
410
|
+
}
|
|
411
|
+
this.current[key] = value;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/traits.ts
|
|
416
|
+
/**
|
|
417
|
+
* Merge traits into a collection of styles
|
|
418
|
+
*/
|
|
419
|
+
function mergeTraits(...traits) {
|
|
420
|
+
return traits.reduce((previous, { styles }) => {
|
|
421
|
+
previous.root = mergeStyles(previous.root, styles.root);
|
|
422
|
+
Object.assign(previous.nested, styles.nested);
|
|
423
|
+
return previous;
|
|
424
|
+
}, {
|
|
425
|
+
nested: {},
|
|
426
|
+
root: {}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Define a trait
|
|
431
|
+
*/
|
|
432
|
+
function defineTrait(build) {
|
|
433
|
+
return build(defineRelativeFragment);
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region src/utils.ts
|
|
437
|
+
/**
|
|
438
|
+
* Cast a value
|
|
439
|
+
*/
|
|
440
|
+
function cast(value) {
|
|
441
|
+
return value;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Resolve an eager value or a thunk
|
|
445
|
+
*/
|
|
446
|
+
function resolve(value) {
|
|
447
|
+
return typeof value === "function" ? value() : value;
|
|
448
|
+
}
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/elements.ts
|
|
451
|
+
/**
|
|
452
|
+
* Define an element
|
|
453
|
+
*/
|
|
454
|
+
function defineElement(styles = {}) {
|
|
455
|
+
return new Element().css(styles);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Resolve element providers to concrete elements
|
|
459
|
+
*/
|
|
460
|
+
function resolveElements(providers) {
|
|
461
|
+
return Object.entries(providers).reduce((els, [name, provider]) => {
|
|
462
|
+
els[name] = resolve(provider).current;
|
|
463
|
+
return els;
|
|
464
|
+
}, {});
|
|
465
|
+
}
|
|
466
|
+
var Element = class extends Builder {
|
|
467
|
+
/**
|
|
468
|
+
* Create an element
|
|
469
|
+
*/
|
|
470
|
+
constructor() {
|
|
471
|
+
super({
|
|
472
|
+
children: {},
|
|
473
|
+
nested: [],
|
|
474
|
+
styles: {}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Define child elements
|
|
479
|
+
*/
|
|
480
|
+
children(definitions) {
|
|
481
|
+
this.setStateField("children", resolveElements(resolve(definitions)));
|
|
482
|
+
return this;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Apply styles to the element
|
|
486
|
+
*/
|
|
487
|
+
css(styles) {
|
|
488
|
+
const { current } = this;
|
|
489
|
+
current.styles = mergeStyles(current.styles, styles);
|
|
490
|
+
return this;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Apply traits to the element
|
|
494
|
+
*/
|
|
495
|
+
is(...traits) {
|
|
496
|
+
const { nested, root } = mergeTraits(...traits);
|
|
497
|
+
this.current.styles = mergeStyles(this.current.styles, root);
|
|
498
|
+
if (Object.keys(nested).length > 0) this.current.nested.push(() => nested);
|
|
499
|
+
return this;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Define nested styles for the element
|
|
503
|
+
*/
|
|
504
|
+
nested(provider) {
|
|
505
|
+
this.current.nested.push(provider);
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Define variants for the element
|
|
510
|
+
*/
|
|
511
|
+
variants(provider) {
|
|
512
|
+
this.setStateField("variants", provider);
|
|
513
|
+
return this;
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/internal.ts
|
|
518
|
+
const build = Symbol.for("ruscus/build");
|
|
519
|
+
const identifiers = Symbol.for("ruscus/identifiers");
|
|
520
|
+
/**
|
|
521
|
+
* Apply internal annotations to a component
|
|
522
|
+
*/
|
|
523
|
+
function annotateComponent(component, annotations) {
|
|
524
|
+
const annotate = (key, value) => {
|
|
525
|
+
Object.defineProperty(component, key, {
|
|
526
|
+
configurable: false,
|
|
527
|
+
enumerable: false,
|
|
528
|
+
value,
|
|
529
|
+
writable: false
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
annotate(build, annotations.buildPlan);
|
|
533
|
+
annotate(identifiers, annotations.identifiers);
|
|
534
|
+
return component;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Extract identifiers from a component
|
|
538
|
+
*/
|
|
539
|
+
function extractIdentifiers(component) {
|
|
540
|
+
return component[identifiers];
|
|
541
|
+
}
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/projection.ts
|
|
544
|
+
/**
|
|
545
|
+
* Project a component as class names
|
|
546
|
+
*/
|
|
547
|
+
function projectComponent({ current }, name) {
|
|
548
|
+
return projectTree([current.id], current.elements, {
|
|
549
|
+
format: "names",
|
|
550
|
+
hasVariants: current.variants !== void 0,
|
|
551
|
+
name
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/components.ts
|
|
556
|
+
var Component = class extends Builder {
|
|
557
|
+
id;
|
|
558
|
+
/**
|
|
559
|
+
* Create a component
|
|
560
|
+
*/
|
|
561
|
+
constructor(name, { namespace = [] } = {}) {
|
|
562
|
+
const id = [...namespace, name].filter(Boolean).join("--");
|
|
563
|
+
super({
|
|
564
|
+
elements: {},
|
|
565
|
+
global: [],
|
|
566
|
+
id,
|
|
567
|
+
identifiers: {},
|
|
568
|
+
nested: [],
|
|
569
|
+
styles: {}
|
|
570
|
+
});
|
|
571
|
+
this.id = id;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Project the component into class names and identifiers
|
|
575
|
+
*/
|
|
576
|
+
apply() {
|
|
577
|
+
const name = useNaming(this.current);
|
|
578
|
+
return annotateComponent(projectComponent(this, name), {
|
|
579
|
+
buildPlan: {
|
|
580
|
+
compile: (config = {}) => styleComponent(this.current, config),
|
|
581
|
+
component: this.current
|
|
582
|
+
},
|
|
583
|
+
identifiers: cast(projectIdentifiers(this.current))
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Apply styles to the component root
|
|
588
|
+
*/
|
|
589
|
+
css(styles) {
|
|
590
|
+
const { current } = this;
|
|
591
|
+
current.styles = mergeStyles(current.styles, styles);
|
|
592
|
+
return this;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Define component-specific identifiers
|
|
596
|
+
*/
|
|
597
|
+
identifiers(types) {
|
|
598
|
+
this.setStateField("identifiers", types);
|
|
599
|
+
return this;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Define the component's elements
|
|
603
|
+
*/
|
|
604
|
+
elements(provider) {
|
|
605
|
+
this.setStateField("elements", resolveElements(provider(defineElement, { ids: projectIdentifiers(this.current) })));
|
|
606
|
+
return this;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Apply reusable trait styles and nested rules
|
|
610
|
+
*/
|
|
611
|
+
is(...traits) {
|
|
612
|
+
const { nested, root } = mergeTraits(...traits);
|
|
613
|
+
this.current.styles = mergeStyles(this.current.styles, root);
|
|
614
|
+
if (Object.keys(nested).length > 0) this.current.nested.push(() => nested);
|
|
615
|
+
return this;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Define global styles for the component
|
|
619
|
+
*/
|
|
620
|
+
global(definitions) {
|
|
621
|
+
this.current.global.push(definitions);
|
|
622
|
+
return this;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Define nested styles for the component
|
|
626
|
+
*/
|
|
627
|
+
nested(definitions) {
|
|
628
|
+
this.current.nested.push(definitions);
|
|
629
|
+
return this;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Define variants for the component root
|
|
633
|
+
*/
|
|
634
|
+
variants(provider) {
|
|
635
|
+
this.setStateField("variants", provider);
|
|
636
|
+
return this;
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
/**
|
|
640
|
+
* Define a component
|
|
641
|
+
*/
|
|
642
|
+
function defineComponent(name) {
|
|
643
|
+
return new Component(name);
|
|
644
|
+
}
|
|
645
|
+
//#endregion
|
|
646
|
+
//#region src/queries.ts
|
|
647
|
+
/**
|
|
648
|
+
* Define a provider of named queries
|
|
649
|
+
*/
|
|
650
|
+
function defineQueries(queries) {
|
|
651
|
+
const build = (blocks) => {
|
|
652
|
+
return Object.entries(blocks).reduce((previous, [label, rules]) => {
|
|
653
|
+
const query = queries[label];
|
|
654
|
+
if (rules && query) previous[query] = defineAbsoluteFragment().nested(rules);
|
|
655
|
+
return previous;
|
|
656
|
+
}, {});
|
|
657
|
+
};
|
|
658
|
+
return Object.assign(build, queries);
|
|
659
|
+
}
|
|
660
|
+
//#endregion
|
|
661
|
+
export { defineComponent as component, extractIdentifiers as identifiers, defineQueries as queries, styleComponent as style, defineTrait as trait };
|
package/dist/vite.d.mts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
//#region src/naming.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* An available naming scheme for classes
|
|
5
|
+
*/
|
|
6
|
+
type NamingScheme = 'compact' | 'verbose';
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/vite/plugin.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* Plugin options
|
|
11
|
+
*/
|
|
12
|
+
type PluginOptions = {
|
|
13
|
+
naming?: NamingScheme;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Define the Vite plugin
|
|
17
|
+
*/
|
|
18
|
+
declare function plugin(options?: PluginOptions): Plugin;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { plugin as default };
|
package/dist/vite.mjs
ADDED
|
Binary file
|