@udixio/theme 1.0.0-beta.8 → 1.0.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.
Files changed (199) hide show
  1. package/.eslintrc.mjs +22 -0
  2. package/CHANGELOG.md +174 -0
  3. package/README.md +6 -68
  4. package/dist/API.d.ts +14 -0
  5. package/dist/API.d.ts.map +1 -0
  6. package/dist/LICENSE +202 -0
  7. package/dist/adapter/adapter.abstract.d.ts +10 -0
  8. package/dist/adapter/adapter.abstract.d.ts.map +1 -0
  9. package/dist/adapter/config.interface.d.ts +14 -0
  10. package/dist/adapter/config.interface.d.ts.map +1 -0
  11. package/dist/adapter/define-config.d.ts +3 -0
  12. package/dist/adapter/define-config.d.ts.map +1 -0
  13. package/dist/adapter/file-adapter.mixin.d.ts +18 -0
  14. package/dist/adapter/file-adapter.mixin.d.ts.map +1 -0
  15. package/dist/adapter/index.d.ts +4 -0
  16. package/dist/adapter/index.d.ts.map +1 -0
  17. package/dist/adapters/index.d.ts +3 -0
  18. package/dist/adapters/index.d.ts.map +1 -0
  19. package/dist/adapters/node.adapter.d.ts +7 -0
  20. package/dist/adapters/node.adapter.d.ts.map +1 -0
  21. package/dist/adapters/vite.adapter.d.ts +3 -0
  22. package/dist/adapters/vite.adapter.d.ts.map +1 -0
  23. package/dist/app.container.d.ts +5 -5
  24. package/dist/app.container.d.ts.map +1 -0
  25. package/dist/app.module.d.ts +1 -0
  26. package/dist/app.module.d.ts.map +1 -0
  27. package/dist/bootstrap.d.ts +3 -0
  28. package/dist/bootstrap.d.ts.map +1 -0
  29. package/dist/color/color.api.d.ts +39 -0
  30. package/dist/color/color.api.d.ts.map +1 -0
  31. package/dist/color/color.manager.d.ts +24 -0
  32. package/dist/color/color.manager.d.ts.map +1 -0
  33. package/dist/color/color.module.d.ts +1 -0
  34. package/dist/color/color.module.d.ts.map +1 -0
  35. package/dist/color/color.utils.d.ts +8 -0
  36. package/dist/color/color.utils.d.ts.map +1 -0
  37. package/dist/color/configurable-color.d.ts +31 -0
  38. package/dist/color/configurable-color.d.ts.map +1 -0
  39. package/dist/color/default-color.d.ts +3 -0
  40. package/dist/color/default-color.d.ts.map +1 -0
  41. package/dist/color/index.d.ts +5 -4
  42. package/dist/color/index.d.ts.map +1 -0
  43. package/dist/index.cjs +3215 -0
  44. package/dist/index.d.ts +7 -4
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +3175 -7
  47. package/dist/material-color-utilities/contrastCurve.d.ts +1 -0
  48. package/dist/material-color-utilities/contrastCurve.d.ts.map +1 -0
  49. package/dist/material-color-utilities/dynamic_color.d.ts +89 -76
  50. package/dist/material-color-utilities/dynamic_color.d.ts.map +1 -0
  51. package/dist/material-color-utilities/hct_solver.d.ts +131 -0
  52. package/dist/material-color-utilities/hct_solver.d.ts.map +1 -0
  53. package/dist/material-color-utilities/htc.d.ts +77 -0
  54. package/dist/material-color-utilities/htc.d.ts.map +1 -0
  55. package/dist/material-color-utilities/index.d.ts +1 -0
  56. package/dist/material-color-utilities/index.d.ts.map +1 -0
  57. package/dist/material-color-utilities/toneDeltaPair.d.ts +19 -25
  58. package/dist/material-color-utilities/toneDeltaPair.d.ts.map +1 -0
  59. package/dist/plugin/index.d.ts +4 -0
  60. package/dist/plugin/index.d.ts.map +1 -0
  61. package/dist/plugin/plugin.abstract.d.ts +19 -5
  62. package/dist/plugin/plugin.abstract.d.ts.map +1 -0
  63. package/dist/plugin/plugin.api.d.ts +10 -0
  64. package/dist/plugin/plugin.api.d.ts.map +1 -0
  65. package/dist/plugin/plugin.module.d.ts +1 -0
  66. package/dist/plugin/plugin.module.d.ts.map +1 -0
  67. package/dist/plugins/font/font.plugin.d.ts +50 -0
  68. package/dist/plugins/font/font.plugin.d.ts.map +1 -0
  69. package/dist/plugins/font/index.d.ts +2 -0
  70. package/dist/plugins/font/index.d.ts.map +1 -0
  71. package/dist/plugins/index.d.ts +2 -0
  72. package/dist/plugins/index.d.ts.map +1 -0
  73. package/dist/theme/index.d.ts +7 -3
  74. package/dist/theme/index.d.ts.map +1 -0
  75. package/dist/theme/scheme.d.ts +20 -0
  76. package/dist/theme/scheme.d.ts.map +1 -0
  77. package/dist/theme/scheme.manager.d.ts +31 -0
  78. package/dist/theme/scheme.manager.d.ts.map +1 -0
  79. package/dist/theme/theme.api.d.ts +23 -0
  80. package/dist/theme/theme.api.d.ts.map +1 -0
  81. package/dist/theme/theme.module.d.ts +1 -0
  82. package/dist/theme/theme.module.d.ts.map +1 -0
  83. package/dist/theme/variant.d.ts +36 -0
  84. package/dist/theme/variant.d.ts.map +1 -0
  85. package/dist/theme/variant.manager.d.ts +14 -0
  86. package/dist/theme/variant.manager.d.ts.map +1 -0
  87. package/dist/theme/variants/expressive.variant.d.ts +3 -0
  88. package/dist/theme/variants/expressive.variant.d.ts.map +1 -0
  89. package/dist/theme/variants/index.d.ts +11 -0
  90. package/dist/theme/variants/index.d.ts.map +1 -0
  91. package/dist/theme/variants/neutral.variant.d.ts +3 -0
  92. package/dist/theme/variants/neutral.variant.d.ts.map +1 -0
  93. package/dist/theme/variants/tonal-spot.variant.d.ts +3 -0
  94. package/dist/theme/variants/tonal-spot.variant.d.ts.map +1 -0
  95. package/dist/theme/variants/vibrant.variant.d.ts +3 -0
  96. package/dist/theme/variants/vibrant.variant.d.ts.map +1 -0
  97. package/package.json +24 -86
  98. package/src/API.ts +23 -0
  99. package/src/adapter/adapter.abstract.ts +64 -0
  100. package/src/adapter/config.interface.ts +14 -0
  101. package/src/adapter/define-config.ts +11 -0
  102. package/src/adapter/file-adapter.mixin.ts +72 -0
  103. package/src/adapter/index.ts +3 -0
  104. package/src/adapters/index.ts +2 -0
  105. package/src/adapters/node.adapter.ts +49 -0
  106. package/src/adapters/vite.adapter.ts +79 -0
  107. package/src/app.container.ts +12 -36
  108. package/src/app.module.ts +2 -2
  109. package/src/bootstrap.ts +6 -0
  110. package/src/color/color.api.ts +75 -0
  111. package/src/color/color.manager.ts +213 -0
  112. package/src/color/color.module.ts +4 -4
  113. package/src/color/color.utils.ts +126 -0
  114. package/src/color/configurable-color.ts +67 -0
  115. package/src/color/default-color.ts +832 -0
  116. package/src/color/index.ts +4 -4
  117. package/src/index.test.ts +5 -0
  118. package/src/index.ts +6 -4
  119. package/src/material-color-utilities/dynamic_color.ts +286 -222
  120. package/src/material-color-utilities/hct_solver.ts +536 -0
  121. package/src/material-color-utilities/htc.ts +198 -0
  122. package/src/material-color-utilities/toneDeltaPair.ts +29 -11
  123. package/src/plugin/index.ts +3 -0
  124. package/src/plugin/plugin.abstract.ts +45 -4
  125. package/src/plugin/plugin.api.ts +51 -0
  126. package/src/plugin/plugin.module.ts +2 -2
  127. package/src/plugins/font/font.plugin.ts +203 -0
  128. package/src/plugins/font/index.ts +1 -0
  129. package/src/plugins/index.ts +1 -0
  130. package/src/theme/index.ts +6 -3
  131. package/src/theme/{services/scheme.service.ts → scheme.manager.ts} +39 -19
  132. package/src/theme/{entities/scheme.entity.ts → scheme.ts} +20 -4
  133. package/src/theme/{services/theme.service.ts → theme.api.ts} +23 -19
  134. package/src/theme/theme.module.ts +6 -4
  135. package/src/theme/variant.manager.ts +58 -0
  136. package/src/theme/variant.ts +53 -0
  137. package/src/theme/variants/expressive.variant.ts +81 -0
  138. package/src/theme/variants/index.ts +16 -0
  139. package/src/theme/variants/neutral.variant.ts +45 -0
  140. package/src/theme/variants/tonal-spot.variant.ts +35 -0
  141. package/src/theme/variants/vibrant.variant.ts +72 -0
  142. package/tsconfig.json +13 -0
  143. package/tsconfig.lib.json +33 -0
  144. package/tsconfig.spec.json +36 -0
  145. package/vite.config.ts +54 -0
  146. package/LICENSE +0 -21
  147. package/dist/app.service.d.ts +0 -13
  148. package/dist/color/color.interface.d.ts +0 -8
  149. package/dist/color/entities/color.entity.d.ts +0 -42
  150. package/dist/color/entities/index.d.ts +0 -1
  151. package/dist/color/models/default-color.model.d.ts +0 -3
  152. package/dist/color/models/index.d.ts +0 -1
  153. package/dist/color/services/color-manager.service.d.ts +0 -18
  154. package/dist/color/services/color.service.d.ts +0 -21
  155. package/dist/color/services/index.d.ts +0 -2
  156. package/dist/config/config.interface.d.ts +0 -14
  157. package/dist/config/config.module.d.ts +0 -2
  158. package/dist/config/config.service.d.ts +0 -12
  159. package/dist/config/index.d.ts +0 -2
  160. package/dist/main.d.ts +0 -3
  161. package/dist/plugin/plugin.service.d.ts +0 -9
  162. package/dist/theme/entities/index.d.ts +0 -2
  163. package/dist/theme/entities/scheme.entity.d.ts +0 -15
  164. package/dist/theme/entities/variant.entity.d.ts +0 -7
  165. package/dist/theme/models/index.d.ts +0 -1
  166. package/dist/theme/models/variant.model.d.ts +0 -8
  167. package/dist/theme/services/index.d.ts +0 -3
  168. package/dist/theme/services/scheme.service.d.ts +0 -17
  169. package/dist/theme/services/theme.service.d.ts +0 -22
  170. package/dist/theme/services/variant.service.d.ts +0 -13
  171. package/dist/theme.cjs.development.js +0 -1975
  172. package/dist/theme.cjs.development.js.map +0 -1
  173. package/dist/theme.cjs.production.min.js +0 -2
  174. package/dist/theme.cjs.production.min.js.map +0 -1
  175. package/dist/theme.esm.js +0 -1947
  176. package/dist/theme.esm.js.map +0 -1
  177. package/src/app.service.spec.ts +0 -15
  178. package/src/app.service.ts +0 -23
  179. package/src/color/color.interface.ts +0 -13
  180. package/src/color/entities/color.entity.ts +0 -71
  181. package/src/color/entities/index.ts +0 -1
  182. package/src/color/models/default-color.model.ts +0 -300
  183. package/src/color/models/index.ts +0 -1
  184. package/src/color/services/color-manager.service.ts +0 -191
  185. package/src/color/services/color.service.spec.ts +0 -28
  186. package/src/color/services/color.service.ts +0 -75
  187. package/src/color/services/index.ts +0 -2
  188. package/src/config/config.interface.ts +0 -15
  189. package/src/config/config.module.ts +0 -7
  190. package/src/config/config.service.ts +0 -68
  191. package/src/config/index.ts +0 -2
  192. package/src/main.ts +0 -14
  193. package/src/plugin/plugin.service.ts +0 -26
  194. package/src/theme/entities/index.ts +0 -2
  195. package/src/theme/entities/variant.entity.ts +0 -39
  196. package/src/theme/models/index.ts +0 -1
  197. package/src/theme/models/variant.model.ts +0 -63
  198. package/src/theme/services/index.ts +0 -3
  199. package/src/theme/services/variant.service.ts +0 -52
@@ -15,49 +15,92 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import {
19
- clampDouble,
20
- Contrast,
21
- Hct,
22
- TonalPalette,
23
- } from '@material/material-color-utilities';
18
+ import { clampDouble, Contrast, TonalPalette } from '@material/material-color-utilities';
24
19
  import { ContrastCurve } from './contrastCurve';
25
20
  import { ToneDeltaPair } from './toneDeltaPair';
26
- import { SchemeEntity } from '../theme/entities/scheme.entity';
21
+ import { Scheme } from '../theme/scheme';
22
+ import { Hct } from './htc';
27
23
 
28
24
  /**
29
25
  * @param name The name of the dynamic color. Defaults to empty.
30
- * @param palette Function that provides a TonalPalette given
31
- * SchemeEntity. A TonalPalette is defined by a hue and chroma, so this
32
- * replaces the need to specify hue/chroma. By providing a tonal palette, when
33
- * contrast adjustments are made, intended chroma can be preserved.
34
- * @param tone Function that provides a tone given SchemeEntity.
35
- * @param isBackground Whether this dynamic color is a background, with
36
- * some other color as the foreground. Defaults to false.
26
+ * @param palette Function that provides a TonalPalette given DynamicScheme. A
27
+ * TonalPalette is defined by a hue and chroma, so this replaces the need to
28
+ * specify hue/chroma. By providing a tonal palette, when contrast
29
+ * adjustments are made, intended chroma can be preserved.
30
+ * @param tone Function that provides a tone given DynamicScheme. When not
31
+ * provided, the tone is same as the background tone or 50, when no
32
+ * background is provided.
33
+ * @param chromaMultiplier A factor that multiplies the chroma for this color.
34
+ * Default to 1.
35
+ * @param isBackground Whether this dynamic color is a background, with some
36
+ * other color as the foreground. Defaults to false.
37
37
  * @param background The background of the dynamic color (as a function of a
38
- * `SchemeEntity`), if it exists.
38
+ * `DynamicScheme`), if it exists.
39
39
  * @param secondBackground A second background of the dynamic color (as a
40
- * function of a `SchemeEntity`), if it
41
- * exists.
40
+ * function of a `DynamicScheme`), if it exists.
42
41
  * @param contrastCurve A `ContrastCurve` object specifying how its contrast
43
- * against its background should behave in various contrast levels options.
42
+ * against its background should behave in various contrast levels options.
43
+ * Must used together with `background`. When not provided or resolved as
44
+ * undefined, the contrast curve is calculated based on other constraints.
44
45
  * @param toneDeltaPair A `ToneDeltaPair` object specifying a tone delta
45
- * constraint between two colors. One of them must be the color being
46
- * constructed.
46
+ * constraint between two colors. One of them must be the color being
47
+ * constructed. When not provided or resolved as undefined, the tone is
48
+ * calculated based on other constraints.
47
49
  */
48
- interface FromPaletteOptions {
50
+ export interface FromPaletteOptions {
49
51
  name?: string;
50
- palette: (scheme: SchemeEntity) => TonalPalette;
51
- tone: (scheme: SchemeEntity) => number;
52
+ palette: (scheme: Scheme) => TonalPalette;
53
+ tone?: (scheme: Scheme) => number;
54
+ chromaMultiplier?: (scheme: Scheme) => number;
52
55
  isBackground?: boolean;
53
- background?: (scheme: SchemeEntity) => DynamicColor;
54
- secondBackground?: (scheme: SchemeEntity) => DynamicColor;
55
- contrastCurve?: ContrastCurve;
56
- toneDeltaPair?: (scheme: SchemeEntity) => ToneDeltaPair;
56
+ background?: (scheme: Scheme) => DynamicColor | undefined;
57
+ secondBackground?: (scheme: Scheme) => DynamicColor | undefined;
58
+ contrastCurve?: (scheme: Scheme) => ContrastCurve | undefined;
59
+ toneDeltaPair?: (scheme: Scheme) => ToneDeltaPair | undefined;
57
60
  }
58
61
 
59
62
  /**
60
- * A color that adjusts itself based on UI state provided by SchemeEntity.
63
+ * Returns a new DynamicColor that is the same as the original color, but with
64
+ * the extended dynamic color's constraints for the given spec version.
65
+ *
66
+ * @param originlColor The original color.
67
+ * @param specVersion The spec version to extend.
68
+ * @param extendedColor The color with the values to extend.
69
+ */
70
+ export function extendSpecVersion(
71
+ originlColor: DynamicColor,
72
+ extendedColor: DynamicColor,
73
+ ): DynamicColor {
74
+ return DynamicColor.fromPalette({
75
+ name: originlColor.name,
76
+ palette: (s) => extendedColor.palette(s),
77
+ tone: (s) => extendedColor.tone(s),
78
+ isBackground: originlColor.isBackground,
79
+ chromaMultiplier: (s) => {
80
+ const chromaMultiplier = extendedColor.chromaMultiplier;
81
+ return chromaMultiplier !== undefined ? chromaMultiplier(s) : 1;
82
+ },
83
+ background: (s) => {
84
+ const background = extendedColor.background;
85
+ return background !== undefined ? background(s) : undefined;
86
+ },
87
+ secondBackground: (s) => {
88
+ const secondBackground = extendedColor.secondBackground;
89
+ return secondBackground !== undefined ? secondBackground(s) : undefined;
90
+ },
91
+ contrastCurve: (s) => {
92
+ const contrastCurve = extendedColor.contrastCurve;
93
+ return contrastCurve !== undefined ? contrastCurve(s) : undefined;
94
+ },
95
+ toneDeltaPair: (s) => {
96
+ const toneDeltaPair = extendedColor.toneDeltaPair;
97
+ return toneDeltaPair !== undefined ? toneDeltaPair(s) : undefined;
98
+ },
99
+ });
100
+ }
101
+
102
+ /**
103
+ * A color that adjusts itself based on UI state provided by DynamicScheme.
61
104
  *
62
105
  * Colors without backgrounds do not change tone when contrast changes. Colors
63
106
  * with backgrounds become closer to their background as contrast lowers, and
@@ -68,7 +111,7 @@ interface FromPaletteOptions {
68
111
  * DynamicColor.
69
112
  */
70
113
  export class DynamicColor {
71
- private readonly hctCache = new Map<SchemeEntity, Hct>();
114
+ private readonly hctCache = new Map<Scheme, Hct>();
72
115
 
73
116
  /**
74
117
  * The base constructor for DynamicColor.
@@ -84,50 +127,52 @@ export class DynamicColor {
84
127
  * always, in every case.
85
128
  *
86
129
  * @param name The name of the dynamic color. Defaults to empty.
87
- * @param palette Function that provides a TonalPalette given
88
- * SchemeEntity. A TonalPalette is defined by a hue and chroma, so this
89
- * replaces the need to specify hue/chroma. By providing a tonal palette, when
90
- * contrast adjustments are made, intended chroma can be preserved.
91
- * @param tone Function that provides a tone, given a SchemeEntity.
92
- * @param isBackground Whether this dynamic color is a background, with
93
- * some other color as the foreground. Defaults to false.
130
+ * @param palette Function that provides a TonalPalette given DynamicScheme. A
131
+ * TonalPalette is defined by a hue and chroma, so this replaces the need
132
+ * to specify hue/chroma. By providing a tonal palette, when contrast
133
+ * adjustments are made, intended chroma can be preserved.
134
+ * @param tone Function that provides a tone, given a DynamicScheme.
135
+ * @param isBackground Whether this dynamic color is a background, with some
136
+ * other color as the foreground. Defaults to false.
137
+ * @param chromaMultiplier A factor that multiplies the chroma for this color.
94
138
  * @param background The background of the dynamic color (as a function of a
95
- * `SchemeEntity`), if it exists.
139
+ * `DynamicScheme`), if it exists.
96
140
  * @param secondBackground A second background of the dynamic color (as a
97
- * function of a `SchemeEntity`), if it
98
- * exists.
141
+ * function of a `DynamicScheme`), if it exists.
99
142
  * @param contrastCurve A `ContrastCurve` object specifying how its contrast
100
- * against its background should behave in various contrast levels options.
143
+ * against its background should behave in various contrast levels
144
+ * options.
101
145
  * @param toneDeltaPair A `ToneDeltaPair` object specifying a tone delta
102
- * constraint between two colors. One of them must be the color being
103
- * constructed.
146
+ * constraint between two colors. One of them must be the color being
147
+ * constructed.
104
148
  */
105
149
  constructor(
106
150
  readonly name: string,
107
- readonly palette: (scheme: SchemeEntity) => TonalPalette,
108
- readonly tone: (scheme: SchemeEntity) => number,
151
+ readonly palette: (scheme: Scheme) => TonalPalette,
152
+ readonly tone: (scheme: Scheme) => number,
109
153
  readonly isBackground: boolean,
110
- readonly background?: (scheme: SchemeEntity) => DynamicColor,
111
- readonly secondBackground?: (scheme: SchemeEntity) => DynamicColor,
112
- readonly contrastCurve?: ContrastCurve,
113
- readonly toneDeltaPair?: (scheme: SchemeEntity) => ToneDeltaPair
154
+ readonly chromaMultiplier?: (scheme: Scheme) => number,
155
+ readonly background?: (scheme: Scheme) => DynamicColor | undefined,
156
+ readonly secondBackground?: (scheme: Scheme) => DynamicColor | undefined,
157
+ readonly contrastCurve?: (scheme: Scheme) => ContrastCurve | undefined,
158
+ readonly toneDeltaPair?: (scheme: Scheme) => ToneDeltaPair | undefined,
114
159
  ) {
115
160
  if (!background && secondBackground) {
116
161
  throw new Error(
117
162
  `Color ${name} has secondBackground` +
118
- `defined, but background is not defined.`
163
+ `defined, but background is not defined.`,
119
164
  );
120
165
  }
121
166
  if (!background && contrastCurve) {
122
167
  throw new Error(
123
168
  `Color ${name} has contrastCurve` +
124
- `defined, but background is not defined.`
169
+ `defined, but background is not defined.`,
125
170
  );
126
171
  }
127
172
  if (background && !contrastCurve) {
128
173
  throw new Error(
129
174
  `Color ${name} has background` +
130
- `defined, but contrastCurve is not defined.`
175
+ `defined, but contrastCurve is not defined.`,
131
176
  );
132
177
  }
133
178
  }
@@ -135,24 +180,34 @@ export class DynamicColor {
135
180
  /**
136
181
  * Create a DynamicColor defined by a TonalPalette and HCT tone.
137
182
  *
138
- * @param args Functions with SchemeEntity as input. Must provide a palette
139
- * and tone. May provide a background DynamicColor and ToneDeltaConstraint.
183
+ * @param args Functions with DynamicScheme as input. Must provide a palette
184
+ * and tone. May provide a background DynamicColor and ToneDeltaPair.
140
185
  */
141
186
  static fromPalette(args: FromPaletteOptions): DynamicColor {
142
187
  return new DynamicColor(
143
188
  args.name ?? '',
144
189
  args.palette,
145
- args.tone,
190
+ args.tone ?? DynamicColor.getInitialToneFromBackground(args.background),
146
191
  args.isBackground ?? false,
192
+ args.chromaMultiplier,
147
193
  args.background,
148
194
  args.secondBackground,
149
195
  args.contrastCurve,
150
- args.toneDeltaPair
196
+ args.toneDeltaPair,
151
197
  );
152
198
  }
153
199
 
200
+ static getInitialToneFromBackground(
201
+ background?: (scheme: Scheme) => DynamicColor | undefined,
202
+ ): (scheme: Scheme) => number {
203
+ if (background === undefined) {
204
+ return (s) => 50;
205
+ }
206
+ return (s) => (background(s) ? background(s)!.getTone(s) : 50);
207
+ }
208
+
154
209
  /**
155
- * Given a background tone, find a foreground tone, while ensuring they reach
210
+ * Given a background tone, finds a foreground tone, while ensuring they reach
156
211
  * a contrast ratio that is as close to [ratio] as possible.
157
212
  *
158
213
  * @param bgTone Tone in HCT. Range is 0 to 100, undefined behavior when it
@@ -217,7 +272,7 @@ export class DynamicColor {
217
272
  }
218
273
 
219
274
  /**
220
- * Adjust a tone such that white has 4.5 contrast, if the tone is
275
+ * Adjusts a tone such that white has 4.5 contrast, if the tone is
221
276
  * reasonably close to supporting it.
222
277
  */
223
278
  static enableLightForeground(tone: number): number {
@@ -231,220 +286,229 @@ export class DynamicColor {
231
286
  }
232
287
 
233
288
  /**
234
- * Return a ARGB integer (i.e. a hex code).
289
+ * Returns a deep copy of this DynamicColor.
290
+ */
291
+ clone(): DynamicColor {
292
+ return DynamicColor.fromPalette({
293
+ name: this.name,
294
+ palette: this.palette,
295
+ tone: this.tone,
296
+ isBackground: this.isBackground,
297
+ chromaMultiplier: this.chromaMultiplier,
298
+ background: this.background,
299
+ secondBackground: this.secondBackground,
300
+ contrastCurve: this.contrastCurve,
301
+ toneDeltaPair: this.toneDeltaPair,
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Clears the cache of HCT values for this color. For testing or debugging
307
+ * purposes.
308
+ */
309
+ clearCache() {
310
+ this.hctCache.clear();
311
+ }
312
+
313
+ /**
314
+ * Returns a ARGB integer (i.e. a hex code).
235
315
  *
236
316
  * @param scheme Defines the conditions of the user interface, for example,
237
- * whether or not it is dark mode or light mode, and what the desired
238
- * contrast level is.
317
+ * whether or not it is dark mode or light mode, and what the desired
318
+ * contrast level is.
239
319
  */
240
- getArgb(scheme: SchemeEntity): number {
320
+ getArgb(scheme: Scheme): number {
241
321
  return this.getHct(scheme).toInt();
242
322
  }
243
323
 
244
324
  /**
245
- * Return a color, expressed in the HCT color space, that this
325
+ * Returns a color, expressed in the HCT color space, that this
246
326
  * DynamicColor is under the conditions in scheme.
247
327
  *
248
328
  * @param scheme Defines the conditions of the user interface, for example,
249
- * whether or not it is dark mode or light mode, and what the desired
250
- * contrast level is.
329
+ * whether or not it is dark mode or light mode, and what the desired
330
+ * contrast level is.
251
331
  */
252
- getHct(scheme: SchemeEntity): Hct {
253
- const cachedAnswer = this.hctCache.get(scheme);
254
- if (cachedAnswer != null) {
255
- return cachedAnswer;
256
- }
332
+ getHct(scheme: Scheme): Hct {
333
+ const palette = this.palette(scheme);
257
334
  const tone = this.getTone(scheme);
258
- const answer = this.palette(scheme).getHct(tone);
259
- if (this.hctCache.size > 4) {
260
- this.hctCache.clear();
261
- }
262
- this.hctCache.set(scheme, answer);
263
- return answer;
335
+ const hue = palette.hue;
336
+ const chroma =
337
+ palette.chroma *
338
+ (this.chromaMultiplier ? this.chromaMultiplier(scheme) : 1);
339
+
340
+ return Hct.from(hue, chroma, tone);
264
341
  }
265
342
 
266
343
  /**
267
- * Return a tone, T in the HCT color space, that this DynamicColor is under
344
+ * Returns a tone, T in the HCT color space, that this DynamicColor is under
268
345
  * the conditions in scheme.
269
346
  *
270
347
  * @param scheme Defines the conditions of the user interface, for example,
271
- * whether or not it is dark mode or light mode, and what the desired
272
- * contrast level is.
348
+ * whether or not it is dark mode or light mode, and what the desired
349
+ * contrast level is.
273
350
  */
274
- getTone(scheme: SchemeEntity): number {
275
- const decreasingContrast = scheme.contrastLevel < 0;
351
+ getTone(scheme: Scheme): number {
352
+ const toneDeltaPair = this.toneDeltaPair
353
+ ? this.toneDeltaPair(scheme)
354
+ : undefined;
276
355
 
277
- // Case 1: dual foreground, pair of colors with delta constraint.
278
- if (this.toneDeltaPair) {
279
- const toneDeltaPair = this.toneDeltaPair(scheme);
356
+ // Case 0: tone delta constraint.
357
+ if (toneDeltaPair) {
280
358
  const roleA = toneDeltaPair.roleA;
281
359
  const roleB = toneDeltaPair.roleB;
282
- const delta = toneDeltaPair.delta;
283
360
  const polarity = toneDeltaPair.polarity;
284
- const stayTogether = toneDeltaPair.stayTogether;
285
-
286
- const bg = this.background!(scheme);
287
- const bgTone = bg.getTone(scheme);
288
-
289
- const aIsNearer =
290
- polarity === 'nearer' ||
291
- (polarity === 'lighter' && !scheme.isDark) ||
292
- (polarity === 'darker' && scheme.isDark);
293
- const nearer = aIsNearer ? roleA : roleB;
294
- const farther = aIsNearer ? roleB : roleA;
295
- const amNearer = this.name === nearer.name;
296
- const expansionDir = scheme.isDark ? 1 : -1;
297
-
298
- // 1st round: solve to min, each
299
- const nContrast = nearer.contrastCurve!.get(scheme.contrastLevel);
300
- const fContrast = farther.contrastCurve!.get(scheme.contrastLevel);
301
-
302
- // If a color is good enough, it is not adjusted.
303
- // Initial and adjusted tones for `nearer`
304
- const nInitialTone = nearer.tone(scheme);
305
- let nTone =
306
- Contrast.ratioOfTones(bgTone, nInitialTone) >= nContrast
307
- ? nInitialTone
308
- : DynamicColor.foregroundTone(bgTone, nContrast);
309
- // Initial and adjusted tones for `farther`
310
- const fInitialTone = farther.tone(scheme);
311
- let fTone =
312
- Contrast.ratioOfTones(bgTone, fInitialTone) >= fContrast
313
- ? fInitialTone
314
- : DynamicColor.foregroundTone(bgTone, fContrast);
315
-
316
- if (decreasingContrast) {
317
- // If decreasing contrast, adjust color to the "bare minimum"
318
- // that satisfies contrast.
319
- nTone = DynamicColor.foregroundTone(bgTone, nContrast);
320
- fTone = DynamicColor.foregroundTone(bgTone, fContrast);
321
- }
322
-
323
- if ((fTone - nTone) * expansionDir >= delta) {
324
- // Good! Tones satisfy the constraint; no change needed.
325
- } else {
326
- // 2nd round: expand farther to match delta.
327
- fTone = clampDouble(0, 100, nTone + delta * expansionDir);
328
- if ((fTone - nTone) * expansionDir >= delta) {
329
- // Good! Tones now satisfy the constraint; no change needed.
361
+ const constraint = toneDeltaPair.constraint;
362
+ const absoluteDelta =
363
+ polarity === 'darker' ||
364
+ (polarity === 'relative_lighter' && scheme.isDark) ||
365
+ (polarity === 'relative_darker' && !scheme.isDark)
366
+ ? -toneDeltaPair.delta
367
+ : toneDeltaPair.delta;
368
+
369
+ const amRoleA = this.name === roleA.name;
370
+ const selfRole = amRoleA ? roleA : roleB;
371
+ const refRole = amRoleA ? roleB : roleA;
372
+ let selfTone = selfRole.tone(scheme);
373
+ const refTone = refRole.getTone(scheme);
374
+ const relativeDelta = absoluteDelta * (amRoleA ? 1 : -1);
375
+
376
+ if (constraint === 'exact') {
377
+ selfTone = clampDouble(0, 100, refTone + relativeDelta);
378
+ } else if (constraint === 'nearer') {
379
+ if (relativeDelta > 0) {
380
+ selfTone = clampDouble(
381
+ 0,
382
+ 100,
383
+ clampDouble(refTone, refTone + relativeDelta, selfTone),
384
+ );
330
385
  } else {
331
- // 3rd round: contract nearer to match delta.
332
- nTone = clampDouble(0, 100, fTone - delta * expansionDir);
386
+ selfTone = clampDouble(
387
+ 0,
388
+ 100,
389
+ clampDouble(refTone + relativeDelta, refTone, selfTone),
390
+ );
391
+ }
392
+ } else if (constraint === 'farther') {
393
+ if (relativeDelta > 0) {
394
+ selfTone = clampDouble(refTone + relativeDelta, 100, selfTone);
395
+ } else {
396
+ selfTone = clampDouble(0, refTone + relativeDelta, selfTone);
333
397
  }
334
398
  }
335
399
 
336
- // Avoids the 50-59 awkward zone.
337
- if (50 <= nTone && nTone < 60) {
338
- // If `nearer` is in the awkward zone, move it away, together with
339
- // `farther`.
340
- if (expansionDir > 0) {
341
- nTone = 60;
342
- fTone = Math.max(fTone, nTone + delta * expansionDir);
343
- } else {
344
- nTone = 49;
345
- fTone = Math.min(fTone, nTone + delta * expansionDir);
400
+ if (this.background && this.contrastCurve) {
401
+ const background = this.background(scheme);
402
+ const contrastCurve = this.contrastCurve(scheme);
403
+ if (background && contrastCurve) {
404
+ // Adjust the tones for contrast, if background and contrast curve
405
+ // are defined.
406
+ const bgTone = background.getTone(scheme);
407
+ const selfContrast = contrastCurve.get(scheme.contrastLevel);
408
+ selfTone =
409
+ Contrast.ratioOfTones(bgTone, selfTone) >= selfContrast &&
410
+ scheme.contrastLevel >= 0
411
+ ? selfTone
412
+ : DynamicColor.foregroundTone(bgTone, selfContrast);
346
413
  }
347
- } else if (50 <= fTone && fTone < 60) {
348
- if (stayTogether) {
349
- // Fixes both, to avoid two colors on opposite sides of the "awkward
350
- // zone".
351
- if (expansionDir > 0) {
352
- nTone = 60;
353
- fTone = Math.max(fTone, nTone + delta * expansionDir);
354
- } else {
355
- nTone = 49;
356
- fTone = Math.min(fTone, nTone + delta * expansionDir);
357
- }
414
+ }
415
+
416
+ // This can avoid the awkward tones for background colors including the
417
+ // access fixed colors. Accent fixed dim colors should not be adjusted.
418
+ if (this.isBackground && !this.name.endsWith('_fixed_dim')) {
419
+ if (selfTone >= 57) {
420
+ selfTone = clampDouble(65, 100, selfTone);
358
421
  } else {
359
- // Not required to stay together; fixes just one.
360
- if (expansionDir > 0) {
361
- fTone = 60;
362
- } else {
363
- fTone = 49;
364
- }
422
+ selfTone = clampDouble(0, 49, selfTone);
365
423
  }
366
424
  }
367
425
 
368
- // Returns `nTone` if this color is `nearer`, otherwise `fTone`.
369
- return amNearer ? nTone : fTone;
426
+ return selfTone;
370
427
  } else {
371
- // Case 2: No contrast pair; just solve for itself.
428
+ // Case 1: No tone delta pair; just solve for itself.
372
429
  let answer = this.tone(scheme);
373
430
 
374
- if (this.background == null) {
431
+ if (
432
+ this.background == undefined ||
433
+ this.background(scheme) === undefined ||
434
+ this.contrastCurve == undefined ||
435
+ this.contrastCurve(scheme) === undefined
436
+ ) {
375
437
  return answer; // No adjustment for colors with no background.
376
438
  }
377
439
 
378
- const bgTone = this.background(scheme).getTone(scheme);
379
-
380
- const desiredRatio = this.contrastCurve!.get(scheme.contrastLevel);
440
+ const bgTone = this.background(scheme)!.getTone(scheme);
441
+ const desiredRatio = this.contrastCurve(scheme)!.get(
442
+ scheme.contrastLevel,
443
+ );
381
444
 
382
- if (Contrast.ratioOfTones(bgTone, answer) >= desiredRatio) {
383
- // Don't "improve" what's good enough.
384
- } else {
385
- // Rough improvement.
386
- answer = DynamicColor.foregroundTone(bgTone, desiredRatio);
445
+ // Recalculate the tone from desired contrast ratio if the current
446
+ // contrast ratio is not enough or desired contrast level is decreasing
447
+ // (<0).
448
+ answer =
449
+ Contrast.ratioOfTones(bgTone, answer) >= desiredRatio &&
450
+ scheme.contrastLevel >= 0
451
+ ? answer
452
+ : DynamicColor.foregroundTone(bgTone, desiredRatio);
453
+
454
+ // This can avoid the awkward tones for background colors including the
455
+ // access fixed colors. Accent fixed dim colors should not be adjusted.
456
+ if (this.isBackground && !this.name.endsWith('_fixed_dim')) {
457
+ if (answer >= 57) {
458
+ answer = clampDouble(65, 100, answer);
459
+ } else {
460
+ answer = clampDouble(0, 49, answer);
461
+ }
387
462
  }
388
463
 
389
- if (decreasingContrast) {
390
- answer = DynamicColor.foregroundTone(bgTone, desiredRatio);
464
+ if (
465
+ this.secondBackground == undefined ||
466
+ this.secondBackground(scheme) === undefined
467
+ ) {
468
+ return answer;
391
469
  }
392
470
 
393
- if (this.isBackground && 50 <= answer && answer < 60) {
394
- // Must adjust
395
- if (Contrast.ratioOfTones(49, bgTone) >= desiredRatio) {
396
- answer = 49;
397
- } else {
398
- answer = 60;
399
- }
471
+ // Case 2: Adjust for dual backgrounds.
472
+ const [bg1, bg2] = [this.background, this.secondBackground];
473
+ const [bgTone1, bgTone2] = [
474
+ bg1(scheme)!.getTone(scheme),
475
+ bg2(scheme)!.getTone(scheme),
476
+ ];
477
+ const [upper, lower] = [
478
+ Math.max(bgTone1, bgTone2),
479
+ Math.min(bgTone1, bgTone2),
480
+ ];
481
+
482
+ if (
483
+ Contrast.ratioOfTones(upper, answer) >= desiredRatio &&
484
+ Contrast.ratioOfTones(lower, answer) >= desiredRatio
485
+ ) {
486
+ return answer;
400
487
  }
401
488
 
402
- if (this.secondBackground) {
403
- // Case 3: Adjust for dual backgrounds.
404
-
405
- const [bg1, bg2] = [this.background, this.secondBackground];
406
- const [bgTone1, bgTone2] = [
407
- bg1(scheme).getTone(scheme),
408
- bg2(scheme).getTone(scheme),
409
- ];
410
- const [upper, lower] = [
411
- Math.max(bgTone1, bgTone2),
412
- Math.min(bgTone1, bgTone2),
413
- ];
414
-
415
- if (
416
- Contrast.ratioOfTones(upper, answer) >= desiredRatio &&
417
- Contrast.ratioOfTones(lower, answer) >= desiredRatio
418
- ) {
419
- return answer;
420
- }
421
-
422
- // The darkest light tone that satisfies the desired ratio,
423
- // or -1 if such ratio cannot be reached.
424
- const lightOption = Contrast.lighter(upper, desiredRatio);
489
+ // The darkest light tone that satisfies the desired ratio,
490
+ // or -1 if such ratio cannot be reached.
491
+ const lightOption = Contrast.lighter(upper, desiredRatio);
425
492
 
426
- // The lightest dark tone that satisfies the desired ratio,
427
- // or -1 if such ratio cannot be reached.
428
- const darkOption = Contrast.darker(lower, desiredRatio);
493
+ // The lightest dark tone that satisfies the desired ratio,
494
+ // or -1 if such ratio cannot be reached.
495
+ const darkOption = Contrast.darker(lower, desiredRatio);
429
496
 
430
- // Tones suitable for the foreground.
431
- const availables = [];
432
- if (lightOption !== -1) availables.push(lightOption);
433
- if (darkOption !== -1) availables.push(darkOption);
497
+ // Tones suitable for the foreground.
498
+ const availables = [];
499
+ if (lightOption !== -1) availables.push(lightOption);
500
+ if (darkOption !== -1) availables.push(darkOption);
434
501
 
435
- const prefersLight =
436
- DynamicColor.tonePrefersLightForeground(bgTone1) ||
437
- DynamicColor.tonePrefersLightForeground(bgTone2);
438
- if (prefersLight) {
439
- return lightOption < 0 ? 100 : lightOption;
440
- }
441
- if (availables.length === 1) {
442
- return availables[0];
443
- }
444
- return darkOption < 0 ? 0 : darkOption;
502
+ const prefersLight =
503
+ DynamicColor.tonePrefersLightForeground(bgTone1) ||
504
+ DynamicColor.tonePrefersLightForeground(bgTone2);
505
+ if (prefersLight) {
506
+ return lightOption < 0 ? 100 : lightOption;
445
507
  }
446
-
447
- return answer;
508
+ if (availables.length === 1) {
509
+ return availables[0];
510
+ }
511
+ return darkOption < 0 ? 0 : darkOption;
448
512
  }
449
513
  }
450
514
  }