@rokkit/core 1.3.0 → 1.3.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/dist/index.d.ts CHANGED
@@ -5,11 +5,11 @@ export * from "./utils.js";
5
5
  export * from "./nested.js";
6
6
  export * from "./string.js";
7
7
  export * from "./theme.js";
8
- export { ColorSpace } from "./color-space.js";
9
8
  export { FieldMapper } from "./field-mapper.js";
10
9
  export { createEmitter } from "./events.js";
11
10
  export { getLineTypes } from "./connector.js";
12
11
  export { generateTicks } from "./ticks.js";
13
12
  export { getNestedFields } from "./mapping.js";
13
+ export { ColorSpace, relativeLuminance } from "./color-space.js";
14
14
  export { getItemAtIndex, getIndexForItem } from "./mapped-items.js";
15
15
  export { weekdays, getCalendarDays } from "./calendar.js";
package/dist/theme.d.ts CHANGED
@@ -46,6 +46,34 @@ export declare function themeRules(mapping: {
46
46
  * @returns {Array} Array of shortcut definitions
47
47
  */
48
48
  export declare function contrastShortcuts(name: any, onColor?: string): (RegExp | (([, end]: [any, any]) => string))[][];
49
+ /**
50
+ * Near-black / near-white on-color endpoints. Not pure #000/#fff — a hair of
51
+ * lift reads softer on a saturated fill while still clearing AA (near-black is
52
+ * dark enough that even a mid-bright vermillion 500 reaches ~5:1).
53
+ *
54
+ * Exported so consumers (e.g. the demo skin engine) reuse the EXACT library
55
+ * derivation rather than re-deriving on-colors and drifting from `Theme`.
56
+ */
57
+ export declare const ON_COLOR_DARK = "#161616";
58
+ export declare const ON_COLOR_LIGHT = "#fafafa";
59
+ /**
60
+ * Fill luminance where ON_COLOR_DARK and ON_COLOR_LIGHT give equal WCAG contrast:
61
+ * solve (Yf+0.05)² = (Y_dark+0.05)(Y_light+0.05) with Y_dark≈0.0074, Y_light≈0.956
62
+ * → Yf ≈ 0.190. Fills at/above this take dark text; below take light text. (Truly
63
+ * mid-luminance fills near this point clear neither pair at AA — that's the
64
+ * palette author's choice to make, per "pick a 500 where black or white works".)
65
+ */
66
+ export declare const ON_COLOR_Y_CROSSOVER = 0.19;
67
+ /**
68
+ * Pick the readable on-color hex for a fill of relative luminance `y`.
69
+ * Bright fills (y at/above the crossover) take near-black text; dark fills take
70
+ * near-white. A null `y` means the fill couldn't be measured → near-white
71
+ * (matches the historical default).
72
+ *
73
+ * @param {number | null} y - relative luminance (0–1) of the fill, or null
74
+ * @returns {string} on-color hex
75
+ */
76
+ export declare function pickOnColor(y: number | null): string;
49
77
  /**
50
78
  * Theme class for managing color palettes, mappings, and semantic shortcuts.
51
79
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/core",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Contains core utility functions and classes that can be used in various components.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,6 +46,6 @@
46
46
  "ramda": "^0.32.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@rokkit/icons": "1.3.0"
49
+ "@rokkit/icons": "1.3.2"
50
50
  }
51
51
  }
package/src/index.js CHANGED
@@ -11,7 +11,7 @@ export * from './nested.js'
11
11
  export * from './string.js'
12
12
  // skipcq: JS-E1004 - Needed for exposing all functions
13
13
  export * from './theme.js'
14
- export { ColorSpace } from './color-space.js'
14
+ export { ColorSpace, relativeLuminance } from './color-space.js'
15
15
  export { FieldMapper } from './field-mapper.js'
16
16
  export { getItemAtIndex, getIndexForItem } from './mapped-items.js'
17
17
  export { createEmitter } from './events.js'
package/src/theme.ts CHANGED
@@ -95,9 +95,12 @@ export function contrastShortcuts(name, onColor = '#fafafa') {
95
95
  * Near-black / near-white on-color endpoints. Not pure #000/#fff — a hair of
96
96
  * lift reads softer on a saturated fill while still clearing AA (near-black is
97
97
  * dark enough that even a mid-bright vermillion 500 reaches ~5:1).
98
+ *
99
+ * Exported so consumers (e.g. the demo skin engine) reuse the EXACT library
100
+ * derivation rather than re-deriving on-colors and drifting from `Theme`.
98
101
  */
99
- const ON_COLOR_DARK = '#161616'
100
- const ON_COLOR_LIGHT = '#fafafa'
102
+ export const ON_COLOR_DARK = '#161616'
103
+ export const ON_COLOR_LIGHT = '#fafafa'
101
104
  /**
102
105
  * Fill luminance where ON_COLOR_DARK and ON_COLOR_LIGHT give equal WCAG contrast:
103
106
  * solve (Yf+0.05)² = (Y_dark+0.05)(Y_light+0.05) with Y_dark≈0.0074, Y_light≈0.956
@@ -105,7 +108,21 @@ const ON_COLOR_LIGHT = '#fafafa'
105
108
  * mid-luminance fills near this point clear neither pair at AA — that's the
106
109
  * palette author's choice to make, per "pick a 500 where black or white works".)
107
110
  */
108
- const ON_COLOR_Y_CROSSOVER = 0.19
111
+ export const ON_COLOR_Y_CROSSOVER = 0.19
112
+
113
+ /**
114
+ * Pick the readable on-color hex for a fill of relative luminance `y`.
115
+ * Bright fills (y at/above the crossover) take near-black text; dark fills take
116
+ * near-white. A null `y` means the fill couldn't be measured → near-white
117
+ * (matches the historical default).
118
+ *
119
+ * @param {number | null} y - relative luminance (0–1) of the fill, or null
120
+ * @returns {string} on-color hex
121
+ */
122
+ export function pickOnColor(y: number | null): string {
123
+ if (y === null) return ON_COLOR_LIGHT
124
+ return y >= ON_COLOR_Y_CROSSOVER ? ON_COLOR_DARK : ON_COLOR_LIGHT
125
+ }
109
126
 
110
127
  /**
111
128
  * Fallback chain for nullable color mappings.
@@ -270,6 +287,7 @@ export class Theme {
270
287
  if (name === 'on-primary') {
271
288
  return this.#adapter.wrap(this.#onColorHex('primary', colors))
272
289
  }
290
+ /* v8 ignore next -- on-primary is the only 'derived' token, so this default is unreachable */
273
291
  return undefined
274
292
  }
275
293
 
@@ -283,8 +301,7 @@ export class Theme {
283
301
  const palette = colors[this.#mapping[role]]
284
302
  const fill = palette?.['500']
285
303
  const y = fill !== undefined ? relativeLuminance(fill, this.#adapter.name) : null
286
- if (y === null) return ON_COLOR_LIGHT
287
- return y >= ON_COLOR_Y_CROSSOVER ? ON_COLOR_DARK : ON_COLOR_LIGHT
304
+ return pickOnColor(y)
288
305
  }
289
306
 
290
307
  #surfaceInkAlias(role: 'surface' | 'ink'): Record<string, string> {
package/src/utils.js CHANGED
@@ -172,12 +172,13 @@ export function getSnippet(obj, key, defaultSnippet = null) {
172
172
  /**
173
173
  * Resolve which snippet to render for a proxy item.
174
174
  *
175
- * Checks proxy.snippet for a per-item named override first (e.g. item.snippet = 'highlighted').
175
+ * Reads the proxy's `snippet` field (via proxy.get('snippet'), honoring fields.snippet)
176
+ * for a per-item named override first (e.g. item.snippet = 'highlighted').
176
177
  * Falls back to the component-level fallback snippet name (e.g. 'itemContent' / 'groupContent').
177
178
  * Returns null if neither is found.
178
179
  *
179
180
  * @param {Record<string, unknown>} snippets - snippets passed to the component
180
- * @param {{ snippet?: string | null }} proxy - any object with an optional .snippet property
181
+ * @param {{ get: (key: string) => unknown }} proxy - a ProxyItem; its snippet field is read via proxy.get('snippet')
181
182
  * @param {string} [fallback] - fallback snippet name; defaults to ITEM_SNIPPET ('itemContent')
182
183
  * @returns {Function | null}
183
184
  */
@@ -190,7 +191,9 @@ function asSnippet(value) {
190
191
  }
191
192
 
192
193
  export function resolveSnippet(snippets, proxy, fallback = ITEM_SNIPPET) {
193
- const name = proxy && proxy.snippet
194
+ // `snippet` is a field-mapped prop like icon/href — read it via the proxy's
195
+ // field accessor (honors fields.snippet), not as a direct property.
196
+ const name = proxy?.get('snippet')
194
197
  return asSnippet(name && snippets[name]) || asSnippet(snippets[fallback])
195
198
  }
196
199