@rafters/shared 0.4.5 → 0.5.0
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/README.md +9 -14
- package/package.json +1 -1
- package/src/index.ts +0 -1
- package/src/types.ts +168 -3
- package/src/version.ts +1 -1
- package/src/fill-signature.ts +0 -217
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
DTCGColorTokenSchema,
|
|
25
25
|
oklchToDTCG,
|
|
26
26
|
parseJSDocIntelligence,
|
|
27
|
-
|
|
27
|
+
FillValueSchema,
|
|
28
28
|
ImportPendingSchema,
|
|
29
29
|
RAFTERS_VERSION,
|
|
30
30
|
} from '@rafters/shared';
|
|
@@ -54,6 +54,14 @@ validate against.
|
|
|
54
54
|
- `ComponentIntelligenceSchema`, `ColorIntelligenceSchema`, `IntelligenceSchema`
|
|
55
55
|
and friends — the shapes the MCP and Studio read when they describe a
|
|
56
56
|
component or a color.
|
|
57
|
+
- `FillValueSchema` / `FillValue` — a fill recipe, the value of a token in the
|
|
58
|
+
`fill` namespace: background, gradient, mask, backdrop, blend, foreground
|
|
59
|
+
and border, with an optional `dark` block and `hover`, `active`, `focus`
|
|
60
|
+
and `disabled` state blocks. `foreground` is required. Every colour in a
|
|
61
|
+
recipe is a registry reference (`{ family, position }` or
|
|
62
|
+
`TokenReferenceSchema`'s `{ token }`), never a literal colour.
|
|
63
|
+
- `FILL_NAME_EXCLUDES` — the names a fill cannot take: Tailwind's own `bg-*`
|
|
64
|
+
utility names and keywords, without the `bg-` prefix.
|
|
57
65
|
- `COMPUTED` — a sentinel symbol. Set a token to it and the system reverts that
|
|
58
66
|
token to its computed value instead of a hardcoded override.
|
|
59
67
|
|
|
@@ -73,19 +81,6 @@ it back out.
|
|
|
73
81
|
for what's missing or malformed.
|
|
74
82
|
- `toDisplayName(name)` — turn a component id into its display name.
|
|
75
83
|
|
|
76
|
-
### Fill signatures (`fill-signature.ts`)
|
|
77
|
-
|
|
78
|
-
`fill=` is a compact color vocabulary, not a styling grammar. `fill="muted/50"`
|
|
79
|
-
is the muted color at 50% alpha; `fill="barbie-pink-to-ken-brown"` is a two-stop
|
|
80
|
-
gradient. `to`, `via`, and `from` are reserved segments.
|
|
81
|
-
|
|
82
|
-
- `parseFillSignature(input)` — parse a fill string into stops and alpha.
|
|
83
|
-
- `validateFillSignature(signature, hasWord)` — check every stop word against a
|
|
84
|
-
vocabulary predicate; returns the first unresolvable word, or `null`.
|
|
85
|
-
- `expandFillSignature(signature, context, hasWord?)` — expand a signature into
|
|
86
|
-
the classes it sets, for the given context (`surface` or `text`).
|
|
87
|
-
- `foregroundWordFor(...)` — pick the readable foreground word for a surface fill.
|
|
88
|
-
|
|
89
84
|
### Import pending (`import-pending.ts`)
|
|
90
85
|
|
|
91
86
|
The schema for tokens found during `rafters init` detection and left awaiting
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -476,6 +476,169 @@ export const ColorReferenceSchema = z.object({
|
|
|
476
476
|
|
|
477
477
|
export type ColorReference = z.infer<typeof ColorReferenceSchema>;
|
|
478
478
|
|
|
479
|
+
// Token reference: names a role or semantic colour token ("card", "primary")
|
|
480
|
+
// and follows that token through the cascade and the mode (FR-RAF-FILL-001).
|
|
481
|
+
export const TokenReferenceSchema = z.object({ token: z.string().min(1) }).strict();
|
|
482
|
+
|
|
483
|
+
export type TokenReference = z.infer<typeof TokenReferenceSchema>;
|
|
484
|
+
|
|
485
|
+
// Fills (FR-RAF-FILL-001): a designer-authored recipe of stock Tailwind parts.
|
|
486
|
+
// Every colour is a registry reference, never a literal.
|
|
487
|
+
const FillColourSchema = z.union([ColorReferenceSchema.strict(), TokenReferenceSchema], {
|
|
488
|
+
error: 'must be a registry reference ({family, position} or {token}), not a literal colour',
|
|
489
|
+
});
|
|
490
|
+
const PercentSchema = z.number().min(0).max(100);
|
|
491
|
+
const FillStopSchema = z
|
|
492
|
+
.object({
|
|
493
|
+
color: FillColourSchema,
|
|
494
|
+
alpha: PercentSchema.optional(),
|
|
495
|
+
position: PercentSchema.optional(),
|
|
496
|
+
})
|
|
497
|
+
.strict();
|
|
498
|
+
const FillPartsSchema = z
|
|
499
|
+
.object({
|
|
500
|
+
background: z
|
|
501
|
+
.object({ color: FillColourSchema, alpha: PercentSchema.optional() })
|
|
502
|
+
.strict()
|
|
503
|
+
.optional(),
|
|
504
|
+
gradient: z
|
|
505
|
+
.object({
|
|
506
|
+
type: z.enum(['linear', 'radial', 'conic']),
|
|
507
|
+
direction: z
|
|
508
|
+
.enum(['to-t', 'to-tr', 'to-r', 'to-br', 'to-b', 'to-bl', 'to-l', 'to-tl'])
|
|
509
|
+
.optional(),
|
|
510
|
+
angle: z.number().optional(),
|
|
511
|
+
from: FillStopSchema.optional(),
|
|
512
|
+
via: FillStopSchema.optional(),
|
|
513
|
+
to: FillStopSchema.optional(),
|
|
514
|
+
interpolation: z
|
|
515
|
+
.enum(['srgb', 'hsl', 'oklab', 'oklch', 'longer', 'shorter', 'increasing', 'decreasing'])
|
|
516
|
+
.optional(),
|
|
517
|
+
})
|
|
518
|
+
.strict()
|
|
519
|
+
.optional(),
|
|
520
|
+
mask: z
|
|
521
|
+
.object({
|
|
522
|
+
type: z.enum(['linear', 'radial', 'conic', 't', 'r', 'b', 'l', 'x', 'y']),
|
|
523
|
+
from: PercentSchema.optional(),
|
|
524
|
+
to: PercentSchema.optional(),
|
|
525
|
+
})
|
|
526
|
+
.strict()
|
|
527
|
+
.optional(),
|
|
528
|
+
backdrop: z
|
|
529
|
+
.object({
|
|
530
|
+
blur: z.enum(['none', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl']).optional(),
|
|
531
|
+
brightness: z.number().min(0).optional(),
|
|
532
|
+
contrast: z.number().min(0).optional(),
|
|
533
|
+
saturate: z.number().min(0).optional(),
|
|
534
|
+
grayscale: PercentSchema.optional(),
|
|
535
|
+
opacity: PercentSchema.optional(),
|
|
536
|
+
})
|
|
537
|
+
.strict()
|
|
538
|
+
.optional(),
|
|
539
|
+
blend: z
|
|
540
|
+
.enum([
|
|
541
|
+
'normal',
|
|
542
|
+
'multiply',
|
|
543
|
+
'screen',
|
|
544
|
+
'overlay',
|
|
545
|
+
'darken',
|
|
546
|
+
'lighten',
|
|
547
|
+
'color-dodge',
|
|
548
|
+
'color-burn',
|
|
549
|
+
'hard-light',
|
|
550
|
+
'soft-light',
|
|
551
|
+
'difference',
|
|
552
|
+
'exclusion',
|
|
553
|
+
'hue',
|
|
554
|
+
'saturation',
|
|
555
|
+
'color',
|
|
556
|
+
'luminosity',
|
|
557
|
+
])
|
|
558
|
+
.optional(),
|
|
559
|
+
foreground: FillColourSchema.optional(),
|
|
560
|
+
border: FillColourSchema.optional(),
|
|
561
|
+
})
|
|
562
|
+
.strict();
|
|
563
|
+
const FillStateSchema = FillPartsSchema.extend({ dark: FillPartsSchema.optional() }).strict();
|
|
564
|
+
|
|
565
|
+
export const FillValueSchema = FillPartsSchema.extend({
|
|
566
|
+
foreground: FillColourSchema,
|
|
567
|
+
dark: FillPartsSchema.optional(),
|
|
568
|
+
hover: FillStateSchema.optional(),
|
|
569
|
+
active: FillStateSchema.optional(),
|
|
570
|
+
focus: FillStateSchema.optional(),
|
|
571
|
+
disabled: FillStateSchema.optional(),
|
|
572
|
+
}).strict();
|
|
573
|
+
|
|
574
|
+
export type FillValue = z.infer<typeof FillValueSchema>;
|
|
575
|
+
|
|
576
|
+
// Names a fill cannot take (FR-RAF-FILL-001): Tailwind's static bg-* utilities
|
|
577
|
+
// (45), the legacy ones it still emits (12) and the bg- keywords, each without
|
|
578
|
+
// its `bg-` prefix. Studio keeps the designer-facing list.
|
|
579
|
+
export const FILL_NAME_EXCLUDES: readonly string[] = [
|
|
580
|
+
'auto',
|
|
581
|
+
'blend-color',
|
|
582
|
+
'blend-color-burn',
|
|
583
|
+
'blend-color-dodge',
|
|
584
|
+
'blend-darken',
|
|
585
|
+
'blend-difference',
|
|
586
|
+
'blend-exclusion',
|
|
587
|
+
'blend-hard-light',
|
|
588
|
+
'blend-hue',
|
|
589
|
+
'blend-lighten',
|
|
590
|
+
'blend-luminosity',
|
|
591
|
+
'blend-multiply',
|
|
592
|
+
'blend-normal',
|
|
593
|
+
'blend-overlay',
|
|
594
|
+
'blend-saturation',
|
|
595
|
+
'blend-screen',
|
|
596
|
+
'blend-soft-light',
|
|
597
|
+
'bottom',
|
|
598
|
+
'bottom-left',
|
|
599
|
+
'bottom-right',
|
|
600
|
+
'center',
|
|
601
|
+
'clip-border',
|
|
602
|
+
'clip-content',
|
|
603
|
+
'clip-padding',
|
|
604
|
+
'clip-text',
|
|
605
|
+
'contain',
|
|
606
|
+
'cover',
|
|
607
|
+
'fixed',
|
|
608
|
+
'left',
|
|
609
|
+
'local',
|
|
610
|
+
'no-repeat',
|
|
611
|
+
'none',
|
|
612
|
+
'origin-border',
|
|
613
|
+
'origin-content',
|
|
614
|
+
'origin-padding',
|
|
615
|
+
'repeat',
|
|
616
|
+
'repeat-round',
|
|
617
|
+
'repeat-space',
|
|
618
|
+
'repeat-x',
|
|
619
|
+
'repeat-y',
|
|
620
|
+
'right',
|
|
621
|
+
'scroll',
|
|
622
|
+
'top',
|
|
623
|
+
'top-left',
|
|
624
|
+
'top-right',
|
|
625
|
+
'left-top',
|
|
626
|
+
'right-top',
|
|
627
|
+
'left-bottom',
|
|
628
|
+
'right-bottom',
|
|
629
|
+
'gradient-to-t',
|
|
630
|
+
'gradient-to-tr',
|
|
631
|
+
'gradient-to-r',
|
|
632
|
+
'gradient-to-br',
|
|
633
|
+
'gradient-to-b',
|
|
634
|
+
'gradient-to-bl',
|
|
635
|
+
'gradient-to-l',
|
|
636
|
+
'gradient-to-tl',
|
|
637
|
+
'inherit',
|
|
638
|
+
'current',
|
|
639
|
+
'transparent',
|
|
640
|
+
];
|
|
641
|
+
|
|
479
642
|
// Progression system labels stamped on tokens to record which math system
|
|
480
643
|
// produced them. Used only by TokenSchema below; the math-utils package
|
|
481
644
|
// carries the actual ratio definitions in DEFAULT_RATIOS. This array will be
|
|
@@ -517,7 +680,7 @@ export type OverrideKind = z.infer<typeof OverrideKindSchema>;
|
|
|
517
680
|
export const TokenSchema = z.object({
|
|
518
681
|
// Core token data
|
|
519
682
|
name: z.string(),
|
|
520
|
-
value: z.union([z.string(), ColorValueSchema, ColorReferenceSchema]), // String, ColorValue for families,
|
|
683
|
+
value: z.union([z.string(), ColorValueSchema, ColorReferenceSchema, FillValueSchema]), // String, ColorValue for families, ColorReference for semantic, FillValue for fills
|
|
521
684
|
category: z.string(),
|
|
522
685
|
namespace: z.string(),
|
|
523
686
|
binding: BindingSchema.optional(), // Plugin binding for cascade-driven values
|
|
@@ -550,7 +713,7 @@ export const TokenSchema = z.object({
|
|
|
550
713
|
userOverride: z
|
|
551
714
|
.object({
|
|
552
715
|
// Value before override (for undo)
|
|
553
|
-
previousValue: z.union([z.string(), ColorValueSchema, ColorReferenceSchema]),
|
|
716
|
+
previousValue: z.union([z.string(), ColorValueSchema, ColorReferenceSchema, FillValueSchema]),
|
|
554
717
|
// Why was this overridden
|
|
555
718
|
reason: z.string(),
|
|
556
719
|
// Additional context (e.g. "Q1 marketing campaign", "accessibility audit")
|
|
@@ -566,7 +729,9 @@ export const TokenSchema = z.object({
|
|
|
566
729
|
|
|
567
730
|
// Computed value from generation rule (before any override)
|
|
568
731
|
// Stored so agents can see what the system WOULD produce vs what human chose
|
|
569
|
-
computedValue: z
|
|
732
|
+
computedValue: z
|
|
733
|
+
.union([z.string(), ColorValueSchema, ColorReferenceSchema, FillValueSchema])
|
|
734
|
+
.optional(),
|
|
570
735
|
|
|
571
736
|
// Dependency tracking for automatic regeneration
|
|
572
737
|
dependsOn: z.array(z.string()).optional(), // Parent token(s) - empty = root token
|
package/src/version.ts
CHANGED
package/src/fill-signature.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fill signature (#1637).
|
|
3
|
-
*
|
|
4
|
-
* A fill is a SIGNATURE: a compact form that sets namespaced classes and
|
|
5
|
-
* existing Tailwind utilities on things. Not a grammar, not an adjective
|
|
6
|
-
* system. The phrase is color vocabulary only:
|
|
7
|
-
*
|
|
8
|
-
* word solid fill="barbie-pink"
|
|
9
|
-
* word/alpha with opacity fill="muted/50" (Tailwind's slash, verbatim)
|
|
10
|
-
* word-to-word 2-stop gradient fill="barbie-pink-to-ken-brown"
|
|
11
|
-
*
|
|
12
|
-
* '-to-' is RESERVED in the color namer (generated names can never contain
|
|
13
|
-
* it), so splitting at the first '-to-' is deterministic without vocabulary.
|
|
14
|
-
* With a vocabulary present, every word must resolve or the signature is
|
|
15
|
-
* rejected naming the unresolvable word (the #1632 lesson: hyphens are
|
|
16
|
-
* overloaded in generated names; split only at reserved separators and
|
|
17
|
-
* validate every side).
|
|
18
|
-
*
|
|
19
|
-
* Everything else -- blur, blend, direction -- is already Tailwind's
|
|
20
|
-
* namespace; our tokens set the VALUES behind those words (what
|
|
21
|
-
* backdrop-blur-sm ends up as). Those words are spoken as plain utilities
|
|
22
|
-
* where class lists are legitimate. Not fill's job.
|
|
23
|
-
*
|
|
24
|
-
* Dark contract: falls out of which utility is emitted. Semantic words
|
|
25
|
-
* compile to utilities over flipping vars (bg-primary follows .dark);
|
|
26
|
-
* family words compile to literal scale utilities (never flip).
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
export interface FillStop {
|
|
30
|
-
/** A vocabulary word: semantic role, family, or family-position. */
|
|
31
|
-
word: string;
|
|
32
|
-
/** Tailwind slash-opacity, integer 0-100. Absent = opaque. */
|
|
33
|
-
alpha?: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface FillSignature {
|
|
37
|
-
/** One stop = solid; two stops = linear gradient (to-b). */
|
|
38
|
-
stops: [FillStop] | [FillStop, FillStop];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type FillParseResult =
|
|
42
|
-
| { ok: true; signature: FillSignature }
|
|
43
|
-
| { ok: false; word: string; reason: string };
|
|
44
|
-
|
|
45
|
-
export type FillContext = 'surface' | 'text';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Hyphen segments reserved across the system (#1637). The fill parser
|
|
49
|
-
* splits at '-to-'; 'via' and 'from' are Tailwind's other gradient
|
|
50
|
-
* connectors and are reserved alongside so they can join the signature
|
|
51
|
-
* without a namer migration. The color namer's bank invariant test
|
|
52
|
-
* imports this list -- one copy of the reservation.
|
|
53
|
-
*/
|
|
54
|
-
export const RESERVED_FILL_SEGMENTS = ['to', 'via', 'from'] as const;
|
|
55
|
-
|
|
56
|
-
const SEPARATOR = `-${RESERVED_FILL_SEGMENTS[0]}-`;
|
|
57
|
-
const ALPHA_PATTERN = /^(100|[1-9]?[0-9])$/;
|
|
58
|
-
const WORD_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
59
|
-
|
|
60
|
-
function parseStop(term: string): FillStop | { word: string; reason: string } {
|
|
61
|
-
const slash = term.indexOf('/');
|
|
62
|
-
const word = slash === -1 ? term : term.slice(0, slash);
|
|
63
|
-
if (!WORD_PATTERN.test(word)) {
|
|
64
|
-
return { word: word || term, reason: 'not a vocabulary word' };
|
|
65
|
-
}
|
|
66
|
-
if (slash === -1) return { word };
|
|
67
|
-
const alphaRaw = term.slice(slash + 1);
|
|
68
|
-
if (!ALPHA_PATTERN.test(alphaRaw)) {
|
|
69
|
-
return { word, reason: `invalid opacity "/${alphaRaw}" (integer 0-100)` };
|
|
70
|
-
}
|
|
71
|
-
return { word, alpha: Number(alphaRaw) };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Parse a fill signature. Syntax only -- vocabulary validation is the
|
|
76
|
-
* caller's job via `validateFillSignature` wherever a registry exists
|
|
77
|
-
* (build, tests, MCP). Runtime components expand optimistically; the
|
|
78
|
-
* build-time safelist pass is the strict gate.
|
|
79
|
-
*/
|
|
80
|
-
export function parseFillSignature(input: string): FillParseResult {
|
|
81
|
-
const trimmed = input.trim();
|
|
82
|
-
if (trimmed.length === 0) {
|
|
83
|
-
return { ok: false, word: input, reason: 'empty fill' };
|
|
84
|
-
}
|
|
85
|
-
if (trimmed.includes(' ')) {
|
|
86
|
-
return {
|
|
87
|
-
ok: false,
|
|
88
|
-
word: trimmed,
|
|
89
|
-
reason:
|
|
90
|
-
'fill is a single signature -- other utilities are plain Tailwind words in the class list',
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const split = trimmed.indexOf(SEPARATOR);
|
|
95
|
-
if (split === -1) {
|
|
96
|
-
const stop = parseStop(trimmed);
|
|
97
|
-
if ('reason' in stop) return { ok: false, ...stop };
|
|
98
|
-
return { ok: true, signature: { stops: [stop] } };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const left = trimmed.slice(0, split);
|
|
102
|
-
const right = trimmed.slice(split + SEPARATOR.length);
|
|
103
|
-
if (right.includes(SEPARATOR)) {
|
|
104
|
-
return {
|
|
105
|
-
ok: false,
|
|
106
|
-
word: trimmed,
|
|
107
|
-
reason: 'three-stop gradients are not supported (two stops only)',
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const from = parseStop(left);
|
|
111
|
-
if ('reason' in from) return { ok: false, ...from };
|
|
112
|
-
const to = parseStop(right);
|
|
113
|
-
if ('reason' in to) return { ok: false, ...to };
|
|
114
|
-
return { ok: true, signature: { stops: [from, to] } };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Validate every word in a parsed signature against a vocabulary.
|
|
119
|
-
* Returns null when valid, else the first unresolvable word.
|
|
120
|
-
*/
|
|
121
|
-
export function validateFillSignature(
|
|
122
|
-
signature: FillSignature,
|
|
123
|
-
hasWord: (word: string) => boolean,
|
|
124
|
-
): string | null {
|
|
125
|
-
for (const stop of signature.stops) {
|
|
126
|
-
if (!hasWord(stop.word)) return stop.word;
|
|
127
|
-
}
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* The surface roles whose `{word}-foreground` pair is guaranteed by the
|
|
133
|
-
* frozen contract (shadcn drop-in names plus rafters role extensions --
|
|
134
|
-
* doctrine: contract names are frozen interface). Pairing is membership
|
|
135
|
-
* here, never spelling: family words (barbie-pink, blue-300) and unknown
|
|
136
|
-
* words never pair, so no phantom text-*-foreground class ever ships.
|
|
137
|
-
*/
|
|
138
|
-
const PAIRED_SURFACE_ROLES = new Set([
|
|
139
|
-
'background',
|
|
140
|
-
'card',
|
|
141
|
-
'panel',
|
|
142
|
-
'popover',
|
|
143
|
-
'surface',
|
|
144
|
-
'primary',
|
|
145
|
-
'secondary',
|
|
146
|
-
'muted',
|
|
147
|
-
'accent',
|
|
148
|
-
'destructive',
|
|
149
|
-
'success',
|
|
150
|
-
'warning',
|
|
151
|
-
'info',
|
|
152
|
-
'alert',
|
|
153
|
-
'highlight',
|
|
154
|
-
'selection',
|
|
155
|
-
'sidebar',
|
|
156
|
-
'nav',
|
|
157
|
-
'tooltip',
|
|
158
|
-
'overlay',
|
|
159
|
-
'table',
|
|
160
|
-
'table-header',
|
|
161
|
-
'code',
|
|
162
|
-
'badge',
|
|
163
|
-
'avatar',
|
|
164
|
-
'input',
|
|
165
|
-
]);
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* The paired foreground word for a surface word, or null when pairing
|
|
169
|
-
* does not apply. `background` pairs irregularly with `foreground`.
|
|
170
|
-
* With a vocabulary present (`hasWord`), any word whose `-foreground`
|
|
171
|
-
* pair resolves may pair; without one, only the frozen role set does.
|
|
172
|
-
*/
|
|
173
|
-
export function foregroundWordFor(
|
|
174
|
-
word: string,
|
|
175
|
-
hasWord?: (word: string) => boolean,
|
|
176
|
-
): string | null {
|
|
177
|
-
const pair = word === 'background' ? 'foreground' : `${word}-foreground`;
|
|
178
|
-
if (hasWord) return hasWord(pair) ? pair : null;
|
|
179
|
-
return PAIRED_SURFACE_ROLES.has(word) ? pair : null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function stopClass(prefix: string, stop: FillStop): string {
|
|
183
|
-
return stop.alpha === undefined
|
|
184
|
-
? `${prefix}-${stop.word}`
|
|
185
|
-
: `${prefix}-${stop.word}/${stop.alpha}`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Expand a signature to its class words. Surface context emits the
|
|
190
|
-
* background (and the paired foreground when one exists in the
|
|
191
|
-
* vocabulary); text context emits text color, or gradient text via
|
|
192
|
-
* bg-clip-text. Gradients emit Tailwind v4 utilities (bg-linear-to-b --
|
|
193
|
-
* bg-gradient-to-* is a deprecated v3 alias and is never emitted).
|
|
194
|
-
*/
|
|
195
|
-
export function expandFillSignature(
|
|
196
|
-
signature: FillSignature,
|
|
197
|
-
context: FillContext,
|
|
198
|
-
hasWord?: (word: string) => boolean,
|
|
199
|
-
): string {
|
|
200
|
-
const [first, second] = signature.stops;
|
|
201
|
-
|
|
202
|
-
if (!second) {
|
|
203
|
-
if (context === 'text') return stopClass('text', first);
|
|
204
|
-
const parts = [stopClass('bg', first)];
|
|
205
|
-
const fg = foregroundWordFor(first.word, hasWord);
|
|
206
|
-
if (fg) parts.push(`text-${fg}`);
|
|
207
|
-
return parts.join(' ');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const gradient = ['bg-linear-to-b', stopClass('from', first), stopClass('to', second)];
|
|
211
|
-
if (context === 'text') {
|
|
212
|
-
return [...gradient, 'bg-clip-text', 'text-transparent'].join(' ');
|
|
213
|
-
}
|
|
214
|
-
const fg = foregroundWordFor(first.word, hasWord);
|
|
215
|
-
if (fg) gradient.push(`text-${fg}`);
|
|
216
|
-
return gradient.join(' ');
|
|
217
|
-
}
|