@conductor-by-89soone/tokens 0.6.1 → 0.8.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/dist/cli.js CHANGED
@@ -231,9 +231,9 @@ var radius = [
231
231
  semantic("radius.pill", "9999px", "Fully rounded badge, toggle, marker and progress geometry.")
232
232
  ];
233
233
  var borderWidth = [
234
- aliased("border.width.hairline", "stroke.1", "\uAE30\uBCF8 1px \uC5E3\uC9C0."),
235
- aliased("border.width.emphasis", "stroke.2", "\uD45C \uD5E4\uB354 \uADDC\uCE59, \uB9C8\uCEE4 \uB9C1."),
236
- aliased("border.width.rail", "stroke.3", "\uBC30\uB108 \uC0C1\uD0DC \uB808\uC77C, \uB0B4\uBE44 \uD65C\uC131 \uC778\uB514\uCF00\uC774\uD130.")
234
+ aliased("border.width.hairline", "stroke.1", "Default 1px edge."),
235
+ aliased("border.width.emphasis", "stroke.2", "Table header rule, marker ring."),
236
+ aliased("border.width.rail", "stroke.3", "Banner status rail, active navigation indicator.")
237
237
  ];
238
238
  var Z_LAYERS = {
239
239
  base: 0,
@@ -459,9 +459,9 @@ var radius = [
459
459
  semantic("radius.pill", "9999px", "Fully rounded badge, toggle, marker and progress geometry.")
460
460
  ];
461
461
  var borderWidth = [
462
- aliased("border.width.hairline", "stroke.1", "\uAE30\uBCF8 1px \uC5E3\uC9C0."),
463
- aliased("border.width.emphasis", "stroke.2", "\uD45C \uD5E4\uB354 \uADDC\uCE59, \uB9C8\uCEE4 \uB9C1."),
464
- aliased("border.width.rail", "stroke.3", "\uBC30\uB108 \uC0C1\uD0DC \uB808\uC77C, \uB0B4\uBE44 \uD65C\uC131 \uC778\uB514\uCF00\uC774\uD130.")
462
+ aliased("border.width.hairline", "stroke.1", "Default 1px edge."),
463
+ aliased("border.width.emphasis", "stroke.2", "Table header rule, marker ring."),
464
+ aliased("border.width.rail", "stroke.3", "Banner status rail, active navigation indicator.")
465
465
  ];
466
466
  var Z_LAYERS = {
467
467
  base: 0,
@@ -3463,17 +3463,17 @@
3463
3463
  {
3464
3464
  "key": "border.width.hairline",
3465
3465
  "usage": "decorative",
3466
- "reason": "기본 1px 엣지."
3466
+ "reason": "Default 1px edge."
3467
3467
  },
3468
3468
  {
3469
3469
  "key": "border.width.emphasis",
3470
3470
  "usage": "decorative",
3471
- "reason": "표 헤더 규칙, 마커 링."
3471
+ "reason": "Table header rule, marker ring."
3472
3472
  },
3473
3473
  {
3474
3474
  "key": "border.width.rail",
3475
3475
  "usage": "decorative",
3476
- "reason": "배너 상태 레일, 내비 활성 인디케이터."
3476
+ "reason": "Banner status rail, active navigation indicator."
3477
3477
  },
3478
3478
  {
3479
3479
  "key": "z.base",
package/dist/index.d.ts CHANGED
@@ -90,4 +90,42 @@ interface TokenGroup {
90
90
  readonly order: number;
91
91
  }
92
92
 
93
- export { CDT_PREFIX, PACKAGE_NAME, SEVERITY_ICONS, STATUS_ICONS, type SeverityIconName, type StatusIconName, type TokenDefinition, type TokenGroup, type TokenTier, type TokenUsage };
93
+ /**
94
+ * WCAG 2.1 contrast ratio (1–21) of `foreground` drawn on `background`.
95
+ *
96
+ * Accepts `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb()`/`rgba()` and `hsl()`/`hsla()`, alone or as the single
97
+ * colour inside a compound value such as a box-shadow. A translucent
98
+ * foreground is composited over the background first; the background must be opaque, because what
99
+ * it sits on is unknown here. Throws on anything else rather than returning a ratio that could pass.
100
+ */
101
+ declare function contrastRatio(foreground: string, background: string): number;
102
+
103
+ /**
104
+ * OKLab mixing for derived state colours (CR-050).
105
+ *
106
+ * The stylesheet used to tint tone buttons with `color-mix(in srgb, …)` at render time. Mixing in
107
+ * gamma-encoded sRGB darkens and desaturates the midpoints, and a render-time mix is invisible to
108
+ * `checkContrast`. These tints are now computed here, at build time, in OKLab — the Cartesian form
109
+ * of OKLCH, so lightness is perceptually even — and emitted as plain hex. No browser has to support
110
+ * `oklch()` or relative colour syntax, and every derived background can sit in a contrast pair.
111
+ *
112
+ * OKLab rather than polar OKLCH: the surfaces are near-neutral, so their hue is noise, and an OKLCH
113
+ * mix would swing the tint's hue along that noise (a red mixed toward a blue-grey surface drifts
114
+ * purple). Mixing a and b linearly keeps the tone's hue and walks its chroma down evenly.
115
+ *
116
+ * Refs: CR-050 FR-THM-004 FR-TOK-001
117
+ */
118
+ /** An OKLab (L, a, b) triple, or an sRGB triple in [0, 1] internally. */
119
+ type OklabColor = readonly [number, number, number];
120
+ /** sRGB → OKLab (Björn Ottosson's reference matrices). */
121
+ declare function toOklab(hex: string): OklabColor;
122
+ /** OKLab → sRGB hex, clipping each out-of-gamut channel. */
123
+ declare function fromOklab([lightness, a, b]: OklabColor): string;
124
+ /** `weight` of `foreground` over `background`, mixed in OKLab. 0 → background, 1 → foreground. */
125
+ declare function mixOklab(foreground: string, background: string, weight: number): string;
126
+ /** The two tones whose state tints are derived (`tone.accent.*`, `tone.danger.*`). */
127
+ type ToneName = "accent" | "danger";
128
+ /** `tone.<tone>.<state>` → hex for one theme, from that theme's tone colours and raised surface. */
129
+ declare function toneValues(tones: Readonly<Record<ToneName, string>>, surfaceRaised: string): Readonly<Record<string, string>>;
130
+
131
+ export { CDT_PREFIX, type OklabColor, PACKAGE_NAME, SEVERITY_ICONS, STATUS_ICONS, type SeverityIconName, type StatusIconName, type TokenDefinition, type TokenGroup, type TokenTier, type TokenUsage, type ToneName, contrastRatio, fromOklab, mixOklab, toOklab, toneValues };
package/dist/index.js CHANGED
@@ -476,4 +476,174 @@ var SEVERITY_ICONS = {
476
476
  blocked: "shield-x"
477
477
  };
478
478
 
479
- export { CDT_PREFIX, PACKAGE_NAME, SEVERITY_ICONS, STATUS_ICONS, breakpoints, tokens };
479
+ // src/contrast/color.ts
480
+ var HEX_SOURCE = "#([0-9a-f]{3,8})\\b";
481
+ var FUNCTIONAL_SOURCE = "\\b(rgba?|hsla?)\\(\\s*([^()]*)\\)";
482
+ var clamp = (value, low, high) => Math.min(Math.max(value, low), high);
483
+ function fromHex(digits) {
484
+ const byte = (pair) => parseInt(pair.length === 1 ? pair + pair : pair, 16);
485
+ if (digits.length === 3 || digits.length === 4) {
486
+ const [r, g, b, a] = [...digits];
487
+ return { r: byte(r), g: byte(g), b: byte(b), alpha: a === void 0 ? 1 : byte(a) / 255 };
488
+ }
489
+ if (digits.length === 6 || digits.length === 8) {
490
+ const at = (index) => digits.slice(index, index + 2);
491
+ return {
492
+ r: byte(at(0)),
493
+ g: byte(at(2)),
494
+ b: byte(at(4)),
495
+ alpha: digits.length === 8 ? byte(at(6)) / 255 : 1
496
+ };
497
+ }
498
+ return void 0;
499
+ }
500
+ function argumentsOf(body) {
501
+ return body.replace(/\//g, " ").split(/[\s,]+/).filter((part) => part !== "");
502
+ }
503
+ function toAlpha(raw) {
504
+ if (raw === void 0) return 1;
505
+ const value = raw.endsWith("%") ? Number(raw.slice(0, -1)) / 100 : Number(raw);
506
+ return Number.isFinite(value) ? clamp(value, 0, 1) : 1;
507
+ }
508
+ function fromRgb(body) {
509
+ const parts = argumentsOf(body);
510
+ if (parts.length < 3) return void 0;
511
+ const channel = (raw) => {
512
+ const value = raw.endsWith("%") ? Number(raw.slice(0, -1)) / 100 * 255 : Number(raw);
513
+ return Number.isFinite(value) ? clamp(Math.round(value), 0, 255) : Number.NaN;
514
+ };
515
+ const r = channel(parts[0]);
516
+ const g = channel(parts[1]);
517
+ const b = channel(parts[2]);
518
+ if ([r, g, b].some(Number.isNaN)) return void 0;
519
+ return { r, g, b, alpha: toAlpha(parts[3]) };
520
+ }
521
+ function fromHsl(body) {
522
+ const parts = argumentsOf(body);
523
+ if (parts.length < 3) return void 0;
524
+ const hue = Number(parts[0].replace(/deg$/i, ""));
525
+ const saturation = Number(parts[1].replace(/%$/, "")) / 100;
526
+ const lightness = Number(parts[2].replace(/%$/, "")) / 100;
527
+ if (![hue, saturation, lightness].every((value) => Number.isFinite(value))) return void 0;
528
+ const chroma = (1 - Math.abs(2 * clamp(lightness, 0, 1) - 1)) * clamp(saturation, 0, 1);
529
+ const sextant = (hue % 360 + 360) % 360 / 60;
530
+ const secondary = chroma * (1 - Math.abs(sextant % 2 - 1));
531
+ const lift = clamp(lightness, 0, 1) - chroma / 2;
532
+ const wheel = [
533
+ [chroma, secondary, 0],
534
+ [secondary, chroma, 0],
535
+ [0, chroma, secondary],
536
+ [0, secondary, chroma],
537
+ [secondary, 0, chroma],
538
+ [chroma, 0, secondary]
539
+ ];
540
+ const [r, g, b] = wheel[Math.floor(sextant) % 6];
541
+ const channel = (raw) => clamp(Math.round((raw + lift) * 255), 0, 255);
542
+ return { r: channel(r), g: channel(g), b: channel(b), alpha: toAlpha(parts[3]) };
543
+ }
544
+ function parseFunctional(name, body) {
545
+ return name.toLowerCase().startsWith("hsl") ? fromHsl(body) : fromRgb(body);
546
+ }
547
+ function findColors(value) {
548
+ const found = [];
549
+ for (const match of value.matchAll(new RegExp(FUNCTIONAL_SOURCE, "gi"))) {
550
+ const parsed = parseFunctional(match[1], match[2]);
551
+ if (parsed) found.push(parsed);
552
+ }
553
+ for (const match of value.matchAll(new RegExp(HEX_SOURCE, "gi"))) {
554
+ const parsed = fromHex(match[1]);
555
+ if (parsed) found.push(parsed);
556
+ }
557
+ return found;
558
+ }
559
+ function linearise(channel8) {
560
+ const channel = channel8 / 255;
561
+ return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
562
+ }
563
+ function relativeLuminance({ r, g, b }) {
564
+ return 0.2126 * linearise(r) + 0.7152 * linearise(g) + 0.0722 * linearise(b);
565
+ }
566
+ function contrastRatio(one, other) {
567
+ const first = relativeLuminance(one);
568
+ const second = relativeLuminance(other);
569
+ const lighter = Math.max(first, second);
570
+ const darker = Math.min(first, second);
571
+ return (lighter + 0.05) / (darker + 0.05);
572
+ }
573
+ function compositeOver(foreground, background) {
574
+ if (foreground.alpha >= 1) return foreground;
575
+ const mix = (front, back) => Math.round(front * foreground.alpha + back * (1 - foreground.alpha));
576
+ return {
577
+ r: mix(foreground.r, background.r),
578
+ g: mix(foreground.g, background.g),
579
+ b: mix(foreground.b, background.b),
580
+ alpha: 1
581
+ };
582
+ }
583
+
584
+ // src/measure.ts
585
+ function parse(role, value) {
586
+ const colours = findColors(value);
587
+ if (colours.length !== 1) throw new Error(`contrastRatio: ${role} "${value}" must hold exactly one #hex, rgb() or hsl() colour, found ${colours.length}`);
588
+ return colours[0];
589
+ }
590
+ function contrastRatio2(foreground, background) {
591
+ const back = parse("background", background);
592
+ if (back.alpha < 1) throw new Error(`contrastRatio: background "${background}" is translucent; composite it onto its surface first`);
593
+ return contrastRatio(compositeOver(parse("foreground", foreground), back), back);
594
+ }
595
+
596
+ // src/oklab.ts
597
+ var HEX = /^#([0-9a-f]{6})$/i;
598
+ function parseHex(hex) {
599
+ const match = HEX.exec(hex);
600
+ if (match === null) throw new Error(`oklab: expected #rrggbb, got "${hex}"`);
601
+ const digits = match[1];
602
+ return [0, 2, 4].map((offset) => Number.parseInt(digits.slice(offset, offset + 2), 16) / 255);
603
+ }
604
+ var toLinear = (channel) => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
605
+ var toGamma = (channel) => channel <= 31308e-7 ? 12.92 * channel : 1.055 * channel ** (1 / 2.4) - 0.055;
606
+ var clamp2 = (channel) => Math.min(1, Math.max(0, channel));
607
+ function toOklab(hex) {
608
+ const [r, g, b] = parseHex(hex).map(toLinear);
609
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
610
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
611
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
612
+ return [
613
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
614
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
615
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
616
+ ];
617
+ }
618
+ function fromOklab([lightness, a, b]) {
619
+ const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
620
+ const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
621
+ const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
622
+ const linear = [
623
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
624
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
625
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
626
+ ];
627
+ return `#${linear.map((channel) => Math.round(clamp2(toGamma(clamp2(channel))) * 255).toString(16).padStart(2, "0")).join("")}`;
628
+ }
629
+ function mixOklab(foreground, background, weight) {
630
+ if (!(weight >= 0 && weight <= 1)) throw new Error(`oklab: weight must be within [0, 1], got ${weight}`);
631
+ const front = toOklab(foreground);
632
+ const back = toOklab(background);
633
+ return fromOklab(front.map((channel, index) => channel * weight + back[index] * (1 - weight)));
634
+ }
635
+ var TONE_WEIGHTS = {
636
+ background: 0.04,
637
+ border: 0.14,
638
+ backgroundHover: 0.12,
639
+ borderHover: 0.45
640
+ };
641
+ function toneValues(tones, surfaceRaised) {
642
+ const out = {};
643
+ for (const tone of Object.keys(tones)) {
644
+ for (const [state, weight] of Object.entries(TONE_WEIGHTS)) out[`tone.${tone}.${state}`] = mixOklab(tones[tone], surfaceRaised, weight);
645
+ }
646
+ return out;
647
+ }
648
+
649
+ export { CDT_PREFIX, PACKAGE_NAME, SEVERITY_ICONS, STATUS_ICONS, breakpoints, contrastRatio2 as contrastRatio, fromOklab, mixOklab, toOklab, tokens, toneValues };
package/dist/tokens.json CHANGED
@@ -1271,7 +1271,7 @@
1271
1271
  "cssName": "--cdt-border-width-hairline",
1272
1272
  "tier": "semantic",
1273
1273
  "usage": "decorative",
1274
- "description": "기본 1px 엣지.",
1274
+ "description": "Default 1px edge.",
1275
1275
  "alias": "stroke.1",
1276
1276
  "values": {
1277
1277
  "dark": "1px",
@@ -1283,7 +1283,7 @@
1283
1283
  "cssName": "--cdt-border-width-emphasis",
1284
1284
  "tier": "semantic",
1285
1285
  "usage": "decorative",
1286
- "description": "표 헤더 규칙, 마커 링.",
1286
+ "description": "Table header rule, marker ring.",
1287
1287
  "alias": "stroke.2",
1288
1288
  "values": {
1289
1289
  "dark": "2px",
@@ -1295,7 +1295,7 @@
1295
1295
  "cssName": "--cdt-border-width-rail",
1296
1296
  "tier": "semantic",
1297
1297
  "usage": "decorative",
1298
- "description": "배너 상태 레일, 내비 활성 인디케이터.",
1298
+ "description": "Banner status rail, active navigation indicator.",
1299
1299
  "alias": "stroke.3",
1300
1300
  "values": {
1301
1301
  "dark": "3px",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conductor-by-89soone/tokens",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "Theme-aware design tokens and validation CLIs for Conductor.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://89sooner.github.io/design-system/",