@sewadah/selaras 0.2.0 → 0.3.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 +36 -7
- package/dist/module.json +1 -1
- package/dist/module.mjs +18 -15
- package/dist/runtime/components/Badge.d.vue.ts +1 -1
- package/dist/runtime/components/Badge.vue.d.ts +1 -1
- package/dist/runtime/components/ContentToc.vue +14 -7
- package/dist/runtime/components/FileUpload.vue +1 -1
- package/dist/runtime/components/NavigationMenu.vue +2 -0
- package/dist/runtime/components/PinInput.vue +1 -1
- package/dist/runtime/components/ProsePre.d.vue.ts +4 -2
- package/dist/runtime/components/ProsePre.vue +9 -3
- package/dist/runtime/components/ProsePre.vue.d.ts +4 -2
- package/dist/runtime/default-colors.css +1 -1
- package/dist/runtime/internal/ComboboxSelectBase.vue +1 -1
- package/dist/runtime/structural.css +1 -1
- package/dist/runtime/theme/avatar.js +7 -7
- package/dist/runtime/theme/badge.js +2 -2
- package/dist/runtime/theme/checkbox.js +1 -1
- package/dist/runtime/theme/date-picker.js +1 -1
- package/dist/runtime/theme/file-upload.js +2 -2
- package/dist/runtime/theme/icon.js +1 -1
- package/dist/runtime/theme/input-number.js +1 -1
- package/dist/runtime/theme/input.js +1 -1
- package/dist/runtime/theme/pin-input.js +1 -1
- package/dist/runtime/theme/progress.js +2 -2
- package/dist/runtime/theme/prose.d.ts +6 -0
- package/dist/runtime/theme/prose.js +2 -0
- package/dist/runtime/theme/radio-group.js +4 -4
- package/dist/runtime/theme/rating.js +1 -1
- package/dist/runtime/theme/select.js +2 -2
- package/dist/runtime/theme/separator.js +1 -1
- package/dist/runtime/theme/slider.js +2 -2
- package/dist/runtime/theme/splitter-resize-handle.js +1 -1
- package/dist/runtime/theme/switch.js +1 -1
- package/dist/runtime/theme/textarea.js +1 -1
- package/dist/runtime/utils/color-registry.d.ts +3 -1
- package/dist/runtime/utils/color-registry.js +4 -0
- package/dist/runtime/utils/file-icons.d.ts +8 -0
- package/dist/runtime/utils/file-icons.js +131 -0
- package/dist/runtime/utils/functional-tokens.d.ts +1 -1
- package/dist/runtime/utils/functional-tokens.js +1 -1
- package/dist/runtime/utils/navigation-menu.d.ts +3 -0
- package/dist/runtime/utils/seed-colors.d.ts +2 -1
- package/dist/runtime/utils/seed-colors.js +24 -16
- package/dist/tokens/default-colors.tokens.json +100 -100
- package/package.json +20 -9
|
@@ -47,7 +47,7 @@ export const ratingTheme = tv({
|
|
|
47
47
|
// backdrop through it, reading as "not filled" with no shape swap
|
|
48
48
|
// needed - recolored once its own indicator's data-state flips to
|
|
49
49
|
// active.
|
|
50
|
-
fillIcon: "text-transparent transition-colors group-data-[state=active]/step:text-[var(--_selaras-color-
|
|
50
|
+
fillIcon: "text-transparent transition-colors group-data-[state=active]/step:text-[var(--_selaras-color-indicator)]"
|
|
51
51
|
},
|
|
52
52
|
variants: {
|
|
53
53
|
orientation: {
|
|
@@ -44,7 +44,7 @@ export const selectTheme = tv({
|
|
|
44
44
|
viewport: "max-h-72 overflow-y-auto p-1",
|
|
45
45
|
group: "px-2 pt-2 pb-1 text-xs font-medium text-[var(--selaras-resolved-text-muted)]",
|
|
46
46
|
item: "relative flex items-center gap-2 rounded-[var(--selaras-resolved-radius-sm)] py-1.5 ps-2 pe-8 text-sm text-[var(--selaras-resolved-text-default)] outline-none cursor-pointer select-none data-[highlighted]:bg-[var(--selaras-resolved-surface-elevated)] data-[disabled]:opacity-50 data-[disabled]:pointer-events-none",
|
|
47
|
-
itemIndicator: "absolute end-2 flex items-center text-[var(--_selaras-color-
|
|
47
|
+
itemIndicator: "absolute end-2 flex items-center text-[var(--_selaras-color-indicator)]",
|
|
48
48
|
empty: "px-2 py-6 text-center text-sm text-[var(--selaras-resolved-text-muted)]",
|
|
49
49
|
arrow: "fill-[var(--selaras-resolved-surface-default)] stroke-[var(--selaras-resolved-border-default)] stroke-1"
|
|
50
50
|
},
|
|
@@ -72,7 +72,7 @@ export const selectTheme = tv({
|
|
|
72
72
|
// overrides win over color's - see input.ts for why declaration order
|
|
73
73
|
// (not runtime call order) is what decides this.
|
|
74
74
|
invalid: {
|
|
75
|
-
true: { trigger: "ring-[var(--_selaras-color-
|
|
75
|
+
true: { trigger: "ring-[var(--_selaras-color-border)] hover:ring-[var(--_selaras-color-border)] focus:ring-[var(--_selaras-color-focus)] focus-within:ring-[var(--_selaras-color-focus)] data-[state=open]:ring-[var(--_selaras-color-focus)]" }
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
defaultVariants: {
|
|
@@ -10,7 +10,7 @@ export const separatorTheme = tv({
|
|
|
10
10
|
// plain bar; with a label, a second one of these renders after it
|
|
11
11
|
// (see Separator.vue), splitting the available space evenly on both
|
|
12
12
|
// sides of the text.
|
|
13
|
-
line: "shrink-0 border-[var(--_selaras-color-
|
|
13
|
+
line: "shrink-0 border-[var(--_selaras-color-indicator)]",
|
|
14
14
|
// Always muted, independent of `color` - a colored line with
|
|
15
15
|
// matching bright label text reads as garish rather than
|
|
16
16
|
// informative; the label stays legible/neutral regardless of how
|
|
@@ -10,8 +10,8 @@ export const sliderTheme = tv({
|
|
|
10
10
|
// positioned element with no top/bottom of its own collapses to 0
|
|
11
11
|
// height. inset-y-0/inset-x-0 below (in the orientation variant)
|
|
12
12
|
// supply that missing cross-axis size.
|
|
13
|
-
range: "absolute rounded-full bg-[var(--_selaras-color-
|
|
14
|
-
thumb: "block shrink-0 rounded-full bg-[var(--selaras-resolved-surface-default)] shadow-[var(--selaras-resolved-shadow-sm)] ring-2 ring-[var(--_selaras-color-
|
|
13
|
+
range: "absolute rounded-full bg-[var(--_selaras-color-indicator)]",
|
|
14
|
+
thumb: "block shrink-0 rounded-full bg-[var(--selaras-resolved-surface-default)] shadow-[var(--selaras-resolved-shadow-sm)] ring-2 ring-[var(--_selaras-color-indicator)] transition-shadow focus-visible:outline-none focus-visible:ring-4",
|
|
15
15
|
// Own addition - Reka's Slider has no tick/mark concept of its own.
|
|
16
16
|
// Positioned via an inline style (percent along the track), not a
|
|
17
17
|
// variant - see Slider.vue's own `ticks` computed.
|
|
@@ -4,7 +4,7 @@ export const splitterResizeHandleTheme = tv({
|
|
|
4
4
|
// A slim hit area with a thinner visible line inside it - the visible
|
|
5
5
|
// line alone would be too narrow a target to reliably grab.
|
|
6
6
|
root: "group relative shrink-0 bg-transparent outline-none",
|
|
7
|
-
line: "absolute bg-[var(--selaras-resolved-border-default)] transition-colors group-hover:bg-[var(--_selaras-color-
|
|
7
|
+
line: "absolute bg-[var(--selaras-resolved-border-default)] transition-colors group-hover:bg-[var(--_selaras-color-indicator)] group-data-[state=drag]:bg-[var(--_selaras-color-indicator)]"
|
|
8
8
|
},
|
|
9
9
|
variants: {
|
|
10
10
|
color: {
|
|
@@ -74,7 +74,7 @@ export const switchTheme = tv({
|
|
|
74
74
|
// its own to recolor (unlike Radio/Checkbox's ring-inset), but an
|
|
75
75
|
// offset outline left a visible gap between it and the pill, unlike
|
|
76
76
|
// every other invalid state in this library, which sits flush.
|
|
77
|
-
true: { track: "ring-2 ring-[var(--_selaras-color-
|
|
77
|
+
true: { track: "ring-2 ring-[var(--_selaras-color-border)]", thumb: "data-[state=checked]:before:bg-[var(--_selaras-color-fill-hover)]" }
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
defaultVariants: {
|
|
@@ -46,7 +46,7 @@ export const textareaTheme = tv({
|
|
|
46
46
|
// override win over color's - see input.ts for why declaration order
|
|
47
47
|
// (not runtime call order) is what decides this.
|
|
48
48
|
invalid: {
|
|
49
|
-
true: { base: "ring-[var(--_selaras-color-
|
|
49
|
+
true: { base: "ring-[var(--_selaras-color-border)] hover:ring-[var(--_selaras-color-border)] focus:ring-[var(--_selaras-color-focus)]" }
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
defaultVariants: {
|
|
@@ -18,6 +18,8 @@ export interface ColorRecipe {
|
|
|
18
18
|
fillHover: string;
|
|
19
19
|
fillPressed: string;
|
|
20
20
|
onFill: string;
|
|
21
|
+
/** Essential unpaired graphics such as progress and selection indicators. */
|
|
22
|
+
indicator: string;
|
|
21
23
|
subtle: string;
|
|
22
24
|
subtleHover: string;
|
|
23
25
|
subtlePressed: string;
|
|
@@ -28,7 +30,7 @@ export interface ColorRecipe {
|
|
|
28
30
|
border: string;
|
|
29
31
|
focus: string;
|
|
30
32
|
}
|
|
31
|
-
export type ColorRecipeInput = Pick<ColorRecipe, 'fill' | 'onFill' | 'subtle' | 'onSubtle' | 'text' | 'border'> & Partial<Pick<ColorRecipe, 'fillHover' | 'fillPressed' | 'subtleHover' | 'subtlePressed' | 'textHover' | 'textPressed' | 'focus'>>;
|
|
33
|
+
export type ColorRecipeInput = Pick<ColorRecipe, 'fill' | 'onFill' | 'subtle' | 'onSubtle' | 'text' | 'border'> & Partial<Pick<ColorRecipe, 'fillHover' | 'fillPressed' | 'indicator' | 'subtleHover' | 'subtlePressed' | 'textHover' | 'textPressed' | 'focus'>>;
|
|
32
34
|
export type RuntimeColorOverrides = Partial<Record<ColorRole, Partial<ColorRecipeInput>>>;
|
|
33
35
|
export interface RuntimeThemeMode extends ThemeTokenOverrides {
|
|
34
36
|
colors?: RuntimeColorOverrides;
|
|
@@ -38,6 +38,9 @@ export function normalizeColorRecipe(input) {
|
|
|
38
38
|
fillHover: input.fillHover ?? "var(--_selaras-color-fill)",
|
|
39
39
|
fillPressed: input.fillPressed ?? "var(--_selaras-color-fill-hover)",
|
|
40
40
|
onFill: input.onFill,
|
|
41
|
+
// A recipe's border is already authored for visibility against its
|
|
42
|
+
// surrounding surface, making it the safe shorthand for unpaired marks.
|
|
43
|
+
indicator: input.indicator ?? "var(--_selaras-color-border)",
|
|
41
44
|
subtle: input.subtle,
|
|
42
45
|
subtleHover: input.subtleHover ?? "var(--_selaras-color-subtle)",
|
|
43
46
|
subtlePressed: input.subtlePressed ?? "var(--_selaras-color-subtle-hover)",
|
|
@@ -62,6 +65,7 @@ const generatedRoleFields = [
|
|
|
62
65
|
"fill-hover",
|
|
63
66
|
"fill-pressed",
|
|
64
67
|
"on-fill",
|
|
68
|
+
"indicator",
|
|
65
69
|
"subtle",
|
|
66
70
|
"subtle-hover",
|
|
67
71
|
"subtle-pressed",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const defaultFileIcons: Record<string, string>;
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a file-type icon for a code block, preferring a filename's own
|
|
4
|
+
* extension (the more specific signal) over the fence's `language`. Returns
|
|
5
|
+
* undefined when neither maps to a known type, so callers can render no icon
|
|
6
|
+
* rather than a misleading fallback.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveFileIcon(language?: string, filename?: string): string | undefined;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export const defaultFileIcons = {
|
|
2
|
+
"vue": "vscode-icons:file-type-vue",
|
|
3
|
+
"ts": "vscode-icons:file-type-typescript",
|
|
4
|
+
"typescript": "vscode-icons:file-type-typescript",
|
|
5
|
+
"mts": "vscode-icons:file-type-typescript",
|
|
6
|
+
"cts": "vscode-icons:file-type-typescript",
|
|
7
|
+
"tsx": "vscode-icons:file-type-reactts",
|
|
8
|
+
"js": "vscode-icons:file-type-js",
|
|
9
|
+
"javascript": "vscode-icons:file-type-js",
|
|
10
|
+
"mjs": "vscode-icons:file-type-js",
|
|
11
|
+
"cjs": "vscode-icons:file-type-js",
|
|
12
|
+
"jsx": "vscode-icons:file-type-reactjs",
|
|
13
|
+
"html": "vscode-icons:file-type-html",
|
|
14
|
+
"htm": "vscode-icons:file-type-html",
|
|
15
|
+
"css": "vscode-icons:file-type-css",
|
|
16
|
+
"scss": "vscode-icons:file-type-scss",
|
|
17
|
+
"sass": "vscode-icons:file-type-sass",
|
|
18
|
+
"less": "vscode-icons:file-type-less",
|
|
19
|
+
"json": "vscode-icons:file-type-json",
|
|
20
|
+
"jsonc": "vscode-icons:file-type-json",
|
|
21
|
+
"json5": "vscode-icons:file-type-json",
|
|
22
|
+
"md": "vscode-icons:file-type-markdown",
|
|
23
|
+
"markdown": "vscode-icons:file-type-markdown",
|
|
24
|
+
"mdx": "vscode-icons:file-type-markdown",
|
|
25
|
+
"bash": "vscode-icons:file-type-shell",
|
|
26
|
+
"sh": "vscode-icons:file-type-shell",
|
|
27
|
+
"shell": "vscode-icons:file-type-shell",
|
|
28
|
+
"zsh": "vscode-icons:file-type-shell",
|
|
29
|
+
"console": "vscode-icons:file-type-shell",
|
|
30
|
+
"terminal": "vscode-icons:file-type-shell",
|
|
31
|
+
"python": "vscode-icons:file-type-python",
|
|
32
|
+
"py": "vscode-icons:file-type-python",
|
|
33
|
+
"yaml": "vscode-icons:file-type-yaml",
|
|
34
|
+
"yml": "vscode-icons:file-type-yaml",
|
|
35
|
+
"toml": "vscode-icons:file-type-toml",
|
|
36
|
+
"ini": "vscode-icons:file-type-ini",
|
|
37
|
+
"cfg": "vscode-icons:file-type-config",
|
|
38
|
+
"conf": "vscode-icons:file-type-config",
|
|
39
|
+
"config": "vscode-icons:file-type-config",
|
|
40
|
+
"env": "vscode-icons:file-type-dotenv",
|
|
41
|
+
"dotenv": "vscode-icons:file-type-dotenv",
|
|
42
|
+
"go": "vscode-icons:file-type-go",
|
|
43
|
+
"rust": "vscode-icons:file-type-rust",
|
|
44
|
+
"rs": "vscode-icons:file-type-rust",
|
|
45
|
+
"php": "vscode-icons:file-type-php",
|
|
46
|
+
"ruby": "vscode-icons:file-type-ruby",
|
|
47
|
+
"rb": "vscode-icons:file-type-ruby",
|
|
48
|
+
"sql": "vscode-icons:file-type-sql",
|
|
49
|
+
"docker": "vscode-icons:file-type-docker",
|
|
50
|
+
"dockerfile": "vscode-icons:file-type-docker",
|
|
51
|
+
"svelte": "vscode-icons:file-type-svelte",
|
|
52
|
+
"astro": "vscode-icons:file-type-astro",
|
|
53
|
+
"xml": "vscode-icons:file-type-xml",
|
|
54
|
+
"svg": "vscode-icons:file-type-svg",
|
|
55
|
+
"java": "vscode-icons:file-type-java",
|
|
56
|
+
"kotlin": "vscode-icons:file-type-kotlin",
|
|
57
|
+
"kt": "vscode-icons:file-type-kotlin",
|
|
58
|
+
"swift": "vscode-icons:file-type-swift",
|
|
59
|
+
"csharp": "vscode-icons:file-type-csharp",
|
|
60
|
+
"cs": "vscode-icons:file-type-csharp",
|
|
61
|
+
"cpp": "vscode-icons:file-type-cpp",
|
|
62
|
+
"c++": "vscode-icons:file-type-cpp",
|
|
63
|
+
"c": "vscode-icons:file-type-c",
|
|
64
|
+
"vim": "vscode-icons:file-type-vim",
|
|
65
|
+
"lua": "vscode-icons:file-type-lua",
|
|
66
|
+
"nginx": "vscode-icons:file-type-nginx",
|
|
67
|
+
"graphql": "vscode-icons:file-type-graphql",
|
|
68
|
+
"gql": "vscode-icons:file-type-graphql",
|
|
69
|
+
"prisma": "vscode-icons:file-type-prisma",
|
|
70
|
+
"powershell": "vscode-icons:file-type-powershell",
|
|
71
|
+
"ps1": "vscode-icons:file-type-powershell",
|
|
72
|
+
"r": "vscode-icons:file-type-r",
|
|
73
|
+
"elixir": "vscode-icons:file-type-elixir",
|
|
74
|
+
"ex": "vscode-icons:file-type-elixir",
|
|
75
|
+
"exs": "vscode-icons:file-type-elixir",
|
|
76
|
+
"erlang": "vscode-icons:file-type-erlang",
|
|
77
|
+
"erl": "vscode-icons:file-type-erlang",
|
|
78
|
+
"haskell": "vscode-icons:file-type-haskell",
|
|
79
|
+
"hs": "vscode-icons:file-type-haskell",
|
|
80
|
+
"scala": "vscode-icons:file-type-scala",
|
|
81
|
+
"julia": "vscode-icons:file-type-julia",
|
|
82
|
+
"jl": "vscode-icons:file-type-julia",
|
|
83
|
+
"zig": "vscode-icons:file-type-zig",
|
|
84
|
+
"nim": "vscode-icons:file-type-nim",
|
|
85
|
+
"crystal": "vscode-icons:file-type-crystal",
|
|
86
|
+
"solidity": "vscode-icons:file-type-solidity",
|
|
87
|
+
"sol": "vscode-icons:file-type-solidity",
|
|
88
|
+
"terraform": "vscode-icons:file-type-terraform",
|
|
89
|
+
"tf": "vscode-icons:file-type-terraform",
|
|
90
|
+
"cmake": "vscode-icons:file-type-cmake",
|
|
91
|
+
"wasm": "vscode-icons:file-type-wasm",
|
|
92
|
+
"text": "vscode-icons:file-type-text",
|
|
93
|
+
"txt": "vscode-icons:file-type-text",
|
|
94
|
+
"plaintext": "vscode-icons:file-type-text",
|
|
95
|
+
"plain": "vscode-icons:file-type-text",
|
|
96
|
+
"output": "vscode-icons:file-type-text",
|
|
97
|
+
"ansi": "vscode-icons:file-type-text",
|
|
98
|
+
"diff": "vscode-icons:file-type-diff",
|
|
99
|
+
"patch": "vscode-icons:file-type-diff",
|
|
100
|
+
"log": "vscode-icons:file-type-log",
|
|
101
|
+
"image": "vscode-icons:file-type-image",
|
|
102
|
+
"audio": "vscode-icons:file-type-audio",
|
|
103
|
+
"video": "vscode-icons:file-type-video",
|
|
104
|
+
"font": "vscode-icons:file-type-font",
|
|
105
|
+
"zip": "vscode-icons:file-type-zip",
|
|
106
|
+
"git": "vscode-icons:file-type-git",
|
|
107
|
+
"gitignore": "vscode-icons:file-type-git",
|
|
108
|
+
"npm": "vscode-icons:file-type-npm",
|
|
109
|
+
"nuxt": "vscode-icons:file-type-nuxt",
|
|
110
|
+
"ansible": "vscode-icons:file-type-ansible",
|
|
111
|
+
"bicep": "vscode-icons:file-type-bicep"
|
|
112
|
+
};
|
|
113
|
+
function normalizeFileIconKey(value) {
|
|
114
|
+
const key = value?.trim().toLowerCase().replace(/^\./, "");
|
|
115
|
+
return key || void 0;
|
|
116
|
+
}
|
|
117
|
+
function extensionOf(filename) {
|
|
118
|
+
if (!filename)
|
|
119
|
+
return void 0;
|
|
120
|
+
const base = filename.split(/[\\/]/).pop() ?? filename;
|
|
121
|
+
const dot = base.lastIndexOf(".");
|
|
122
|
+
return dot > 0 ? base.slice(dot + 1) : base.replace(/^\./, "");
|
|
123
|
+
}
|
|
124
|
+
export function resolveFileIcon(language, filename) {
|
|
125
|
+
for (const candidate of [extensionOf(filename), language]) {
|
|
126
|
+
const key = normalizeFileIconKey(candidate);
|
|
127
|
+
if (key && defaultFileIcons[key])
|
|
128
|
+
return defaultFileIcons[key];
|
|
129
|
+
}
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Functional colors are independent of a component's selected intent role. */
|
|
2
2
|
export declare const functionalTokenGroups: {
|
|
3
|
-
readonly surface: readonly ["default", "elevated", "inverted"];
|
|
3
|
+
readonly surface: readonly ["canvas", "default", "elevated", "inverted"];
|
|
4
4
|
readonly text: readonly ["default", "muted", "inverted"];
|
|
5
5
|
readonly border: readonly ["default", "muted", "hover"];
|
|
6
6
|
};
|
|
@@ -2,6 +2,9 @@ export interface NavigationMenuItem {
|
|
|
2
2
|
label: string;
|
|
3
3
|
icon?: string;
|
|
4
4
|
to?: string;
|
|
5
|
+
target?: string;
|
|
6
|
+
rel?: string;
|
|
7
|
+
ariaLabel?: string;
|
|
5
8
|
disabled?: boolean;
|
|
6
9
|
/** Force active state - otherwise auto-detected against the current route when `to` is set. */
|
|
7
10
|
active?: boolean;
|
|
@@ -8,7 +8,8 @@ export interface SeedColorOptions {
|
|
|
8
8
|
surfaces?: SeedColorSurfaces;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Preserves one opaque sRGB seed as the resting fill and derives a complete,
|
|
12
|
+
* opaque two-mode web ColorRecipe around it.
|
|
12
13
|
* Custom or unresolved surfaces require explicit recipes instead.
|
|
13
14
|
*/
|
|
14
15
|
export declare function defineColorFromSeed(seed: string, options?: SeedColorOptions): ColorModePair<ColorRecipe>;
|
|
@@ -150,30 +150,38 @@ function requireSeries(mode, family, create) {
|
|
|
150
150
|
invalid(`cannot derive the ${mode} ${family} family while preserving its contrast targets; provide an explicit defineColor() recipe.`);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
+
function deriveFillStates(seed, foreground, mode) {
|
|
154
|
+
const preferredTarget = mode === "light" ? black : white;
|
|
155
|
+
const alternateTarget = preferredTarget === black ? white : black;
|
|
156
|
+
for (const target of [preferredTarget, alternateTarget]) {
|
|
157
|
+
for (const scale of [1, 0.75, 0.5, 0.25, 0.125, 0.0625, 0.03125]) {
|
|
158
|
+
const hover = quantize(mixLab(seed, target, 0.06 * scale));
|
|
159
|
+
const pressed = quantize(mixLab(seed, target, 0.12 * scale));
|
|
160
|
+
const colors = [seed, hover, pressed];
|
|
161
|
+
if (new Set(colors.map(toHex)).size === colors.length && colors.every((color) => contrast(color, foreground) >= textMinimum))
|
|
162
|
+
return [hover, pressed];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
invalid(`cannot derive distinct ${mode} fill states while preserving readable content; provide an explicit defineColor() recipe.`);
|
|
166
|
+
}
|
|
153
167
|
function deriveMode(seed, surface, mode) {
|
|
154
168
|
const direction = contrast(white, surface) > contrast(black, surface) ? 1 : -1;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return [];
|
|
161
|
-
}
|
|
162
|
-
}).sort((left, right) => left.cost + Number(left.collapsed) * 0.02 - (right.cost + Number(right.collapsed) * 0.02));
|
|
163
|
-
const fill = fillCandidates[0];
|
|
164
|
-
if (!fill)
|
|
165
|
-
invalid(`cannot derive the ${mode} fill family while preserving its contrast targets; provide an explicit defineColor() recipe.`);
|
|
166
|
-
const fillColor = fill.colors[0];
|
|
169
|
+
const foreground = contrast(seed, black) >= contrast(seed, white) ? black : white;
|
|
170
|
+
if (contrast(seed, foreground) < textMinimum)
|
|
171
|
+
invalid(`cannot preserve the ${mode} fill while deriving readable content; provide an explicit defineColor() recipe.`);
|
|
172
|
+
const [fillHover, fillPressed] = deriveFillStates(seed, foreground, mode);
|
|
173
|
+
const fillColor = seed;
|
|
167
174
|
const subtle = quantize(mixLab(surface, fillColor, 0.1));
|
|
168
175
|
const subtleHover = quantize(mixLab(surface, fillColor, 0.14));
|
|
169
176
|
const subtlePressed = quantize(mixLab(surface, fillColor, 0.18));
|
|
170
177
|
const text = requireSeries(mode, "text/subtle", () => findStateSeries(seed, direction, 0.03, 3, (color) => [surface, subtle, subtleHover, subtlePressed].every((background) => contrast(color, background) >= textGuard))).colors;
|
|
171
178
|
const border = requireSeries(mode, "border", () => findStateSeries(seed, direction, 0.02, 1, (color) => contrast(color, surface) >= nonTextGuard)).colors[0];
|
|
172
179
|
return {
|
|
173
|
-
fill: toHex(
|
|
174
|
-
fillHover: toHex(
|
|
175
|
-
fillPressed: toHex(
|
|
176
|
-
onFill: toHex(
|
|
180
|
+
fill: toHex(fillColor),
|
|
181
|
+
fillHover: toHex(fillHover),
|
|
182
|
+
fillPressed: toHex(fillPressed),
|
|
183
|
+
onFill: toHex(foreground),
|
|
184
|
+
indicator: toHex(border),
|
|
177
185
|
subtle: toHex(subtle),
|
|
178
186
|
subtleHover: toHex(subtleHover),
|
|
179
187
|
subtlePressed: toHex(subtlePressed),
|