@getvitops/utils 8.2.0-alpha.0 → 8.3.0-alpha.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/CHANGELOG.md +399 -23
- package/dist/{adc-D6aw2qgq.mjs → adc-BiW9BzqA.mjs} +2 -3
- package/dist/{adc-D6aw2qgq.mjs.map → adc-BiW9BzqA.mjs.map} +1 -1
- package/dist/{adc-BoEoogyo.d.mts → adc-DvX3-P7a.d.mts} +2 -4
- package/dist/adc-DvX3-P7a.d.mts.map +1 -0
- package/dist/ads/index.d.mts +37 -57
- package/dist/ads/index.d.mts.map +1 -1
- package/dist/ads/index.mjs +21 -10
- package/dist/ads/index.mjs.map +1 -1
- package/dist/audit/index.d.mts +180 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +509 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/color/index.d.mts +7 -5
- package/dist/color/index.d.mts.map +1 -1
- package/dist/color/index.mjs +10 -5
- package/dist/color/index.mjs.map +1 -1
- package/dist/content.d.mts +78 -0
- package/dist/content.d.mts.map +1 -0
- package/dist/content.mjs +45 -0
- package/dist/content.mjs.map +1 -0
- package/dist/crm/index.d.mts +5 -87
- package/dist/crm/index.d.mts.map +1 -1
- package/dist/crm/index.mjs +10 -19
- package/dist/crm/index.mjs.map +1 -1
- package/dist/email/index.d.mts +1 -11
- package/dist/email/index.d.mts.map +1 -1
- package/dist/email/index.mjs +6 -10
- package/dist/email/index.mjs.map +1 -1
- package/dist/favicon.d.mts +0 -2
- package/dist/favicon.d.mts.map +1 -1
- package/dist/favicon.mjs +1 -2
- package/dist/git/index.d.mts +0 -2
- package/dist/git/index.d.mts.map +1 -1
- package/dist/git/index.mjs +1 -2
- package/dist/hosting/index.d.mts +0 -8
- package/dist/hosting/index.d.mts.map +1 -1
- package/dist/hosting/index.mjs +4 -7
- package/dist/hosting/index.mjs.map +1 -1
- package/dist/{identity-BoRddTx3.mjs → identity-C4yInICW.mjs} +2 -3
- package/dist/{identity-BoRddTx3.mjs.map → identity-C4yInICW.mjs.map} +1 -1
- package/dist/index.d.mts +2 -88
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +36 -52
- package/dist/index.mjs.map +1 -1
- package/dist/indexing/index.d.mts +3 -42
- package/dist/indexing/index.d.mts.map +1 -1
- package/dist/indexing/index.mjs +8 -13
- package/dist/indexing/index.mjs.map +1 -1
- package/dist/{json-store-30CFCbZZ.mjs → json-store-VJKEaoVX.mjs} +2 -3
- package/dist/{json-store-30CFCbZZ.mjs.map → json-store-VJKEaoVX.mjs.map} +1 -1
- package/dist/media/index.d.mts +0 -12
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +7 -13
- package/dist/media/index.mjs.map +1 -1
- package/dist/notify/index.d.mts +64 -44
- package/dist/notify/index.d.mts.map +1 -1
- package/dist/notify/index.mjs +138 -11
- package/dist/notify/index.mjs.map +1 -1
- package/dist/onboarding/index.d.mts +26 -6
- package/dist/onboarding/index.d.mts.map +1 -1
- package/dist/onboarding/index.mjs +92 -5
- package/dist/onboarding/index.mjs.map +1 -1
- package/dist/{plan-BLKikoOG.mjs → plan-B9USuc17.mjs} +3 -4
- package/dist/{plan-BLKikoOG.mjs.map → plan-B9USuc17.mjs.map} +1 -1
- package/dist/{plan-Hq8Pb0Uw.d.mts → plan-CH3Wt5ZV.d.mts} +8 -30
- package/dist/plan-CH3Wt5ZV.d.mts.map +1 -0
- package/dist/plan-CddqsmJJ.d.mts +82 -0
- package/dist/plan-CddqsmJJ.d.mts.map +1 -0
- package/dist/plan-DpHv6NO8.mjs +93 -0
- package/dist/plan-DpHv6NO8.mjs.map +1 -0
- package/dist/{plan-format-A18SvJ7C.mjs → plan-format-CIz6Feg2.mjs} +2 -3
- package/dist/{plan-format-A18SvJ7C.mjs.map → plan-format-CIz6Feg2.mjs.map} +1 -1
- package/dist/{platform-0nlOIy8h.mjs → platform-Cb_RtsF1.mjs} +3 -5
- package/dist/{platform-0nlOIy8h.mjs.map → platform-Cb_RtsF1.mjs.map} +1 -1
- package/dist/reviews/index.d.mts +4 -120
- package/dist/reviews/index.d.mts.map +1 -1
- package/dist/reviews/index.mjs +6 -99
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/reviews/select.d.mts +2 -0
- package/dist/reviews/select.mjs +3 -0
- package/dist/schema/index.d.mts +0 -12
- package/dist/schema/index.d.mts.map +1 -1
- package/dist/schema/index.mjs +6 -12
- package/dist/schema/index.mjs.map +1 -1
- package/dist/security/index.d.mts +0 -2
- package/dist/security/index.d.mts.map +1 -1
- package/dist/security/index.mjs +1 -2
- package/dist/{token-DmmoQ7um.d.mts → token-CHR1O4w5.d.mts} +3 -3
- package/dist/token-CHR1O4w5.d.mts.map +1 -0
- package/dist/{token-DkKC5m23.mjs → token-CJSl9eIS.mjs} +33 -14
- package/dist/token-CJSl9eIS.mjs.map +1 -0
- package/dist/tracking/index.d.mts +1 -9
- package/dist/tracking/index.d.mts.map +1 -1
- package/dist/tracking/index.mjs +3 -4
- package/dist/turnstile/index.d.mts +105 -0
- package/dist/turnstile/index.d.mts.map +1 -0
- package/dist/turnstile/index.mjs +281 -0
- package/dist/turnstile/index.mjs.map +1 -0
- package/dist/{types-7ELci8RQ.d.mts → types-oac2P5or.d.mts} +3 -23
- package/dist/types-oac2P5or.d.mts.map +1 -0
- package/dist/url/index.d.mts +18 -0
- package/dist/url/index.d.mts.map +1 -0
- package/dist/url/index.mjs +34 -0
- package/dist/url/index.mjs.map +1 -0
- package/package.json +43 -2
- package/dist/adc-BoEoogyo.d.mts.map +0 -1
- package/dist/cloudflare-BiK61Oq4.d.mts +0 -27
- package/dist/cloudflare-BiK61Oq4.d.mts.map +0 -1
- package/dist/cloudflare-CD8vwrgj.mjs +0 -92
- package/dist/cloudflare-CD8vwrgj.mjs.map +0 -1
- package/dist/plan-Hq8Pb0Uw.d.mts.map +0 -1
- package/dist/token-DkKC5m23.mjs.map +0 -1
- package/dist/token-DmmoQ7um.d.mts.map +0 -1
- package/dist/types-7ELci8RQ.d.mts.map +0 -1
package/dist/color/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Color from "colorjs.io";
|
|
2
2
|
import { ColorSpace, OKLCH, P3, clone, sRGB, serialize, to, toGamutCSS } from "colorjs.io/fn";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/color/colors.ts
|
|
5
5
|
ColorSpace.register(OKLCH);
|
|
6
6
|
ColorSpace.register(sRGB);
|
|
7
7
|
ColorSpace.register(P3);
|
|
@@ -508,8 +508,13 @@ function contrastLc(fg, bg) {
|
|
|
508
508
|
/**
|
|
509
509
|
* Pick a tinted near-white or near-black foreground for best contrast on `bg`
|
|
510
510
|
* (a whisper of the background's hue mixed into off-white/off-black).
|
|
511
|
+
*
|
|
512
|
+
* `prefer` forces the polarity instead of choosing the better one — for a role
|
|
513
|
+
* whose author wants dark text on a bright fill (or the reverse). Whether that
|
|
514
|
+
* pairing is legible is the caller's contrast check to decide, not this
|
|
515
|
+
* function's.
|
|
511
516
|
*/
|
|
512
|
-
function pickOn(bg, hue) {
|
|
517
|
+
function pickOn(bg, hue, prefer) {
|
|
513
518
|
const h = hue ?? hexToOklch(bg).h;
|
|
514
519
|
const white = oklchToHex({
|
|
515
520
|
l: .98,
|
|
@@ -521,6 +526,8 @@ function pickOn(bg, hue) {
|
|
|
521
526
|
c: .02,
|
|
522
527
|
h
|
|
523
528
|
});
|
|
529
|
+
if (prefer === "light") return white;
|
|
530
|
+
if (prefer === "dark") return black;
|
|
524
531
|
return contrastLc(white, bg) >= contrastLc(black, bg) ? white : black;
|
|
525
532
|
}
|
|
526
533
|
/**
|
|
@@ -548,8 +555,7 @@ function tintedEndpoints(hue, chroma) {
|
|
|
548
555
|
};
|
|
549
556
|
}
|
|
550
557
|
|
|
551
|
-
|
|
552
|
-
//#region src/color/color-harmony.ts
|
|
558
|
+
// src/color/color-harmony.ts
|
|
553
559
|
const HARMONY_STRATEGIES = [
|
|
554
560
|
{
|
|
555
561
|
id: "complement",
|
|
@@ -649,6 +655,5 @@ function suggestFunctionalColorHue(type, existingHues) {
|
|
|
649
655
|
return adjustedHue;
|
|
650
656
|
}
|
|
651
657
|
|
|
652
|
-
//#endregion
|
|
653
658
|
export { COLOR_GROUPS, DEFAULT_FUNCTIONAL_HUES, ENDPOINT_CHROMA, HARMONY_DEFINITIONS, HARMONY_STRATEGIES, HARMONY_TYPES, LIGHTNESS_LADDER, STEP_NAMES, applyHarmonyStrategy, assignStepByLightness, contrastLc, formatColorName, gamutMapOklchToHex, generateOklchPalette, getColorName, getColorNameWithHue, getHarmonySuggestions, hexToOklch, ladderDeviation, oklchStringToHex, oklchToHex, oklchToP3, oklchToSrgb, parseOklch, pickOn, suggestFunctionalColorHue, tintedEndpoints };
|
|
654
659
|
//# sourceMappingURL=index.mjs.map
|
package/dist/color/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["OKLCH_Space","sRGB_Space","P3_Space"],"sources":["../../src/color/colors.ts","../../src/color/color-harmony.ts"],"sourcesContent":["import Color from 'colorjs.io';\nimport {\n OKLCH as OKLCH_Space,\n sRGB as sRGB_Space,\n P3 as P3_Space,\n ColorSpace,\n to,\n toGamutCSS,\n clone,\n serialize,\n type PlainColorObject,\n} from 'colorjs.io/fn';\n\nColorSpace.register(OKLCH_Space);\nColorSpace.register(sRGB_Space);\nColorSpace.register(P3_Space);\n\nexport interface RgbResult {\n r: number;\n g: number;\n b: number;\n inGamut: boolean;\n /** Max channel overshoot beyond [0,1] before clamping. 0 when in gamut. */\n gamutDistance: number;\n}\n\n/**\n * Convert OKLCh to sRGB, returning 0-255 clamped values with gamut flag.\n * Fast path for per-pixel canvas rendering (no gamut mapping, just clamp).\n */\nexport function oklchToSrgb(l: number, c: number, h: number): RgbResult {\n const col: PlainColorObject = { space: OKLCH_Space, coords: [l, c, h], alpha: 1 };\n const rgb = to(col, sRGB_Space);\n const [r, g, b] = rgb.coords;\n const inGamut = r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1;\n const gamutDistance = inGamut ? 0 : Math.max(-r, r - 1, -g, g - 1, -b, b - 1, 0);\n return {\n r: Math.max(0, Math.min(255, Math.round(r * 255))),\n g: Math.max(0, Math.min(255, Math.round(g * 255))),\n b: Math.max(0, Math.min(255, Math.round(b * 255))),\n inGamut,\n gamutDistance,\n };\n}\n\n/**\n * Convert OKLCh to Display P3, returning 0-255 clamped values with gamut flag.\n * Fast path for per-pixel canvas rendering on P3 displays.\n */\nexport function oklchToP3(l: number, c: number, h: number): RgbResult {\n const col: PlainColorObject = { space: OKLCH_Space, coords: [l, c, h], alpha: 1 };\n const rgb = to(col, P3_Space);\n const [r, g, b] = rgb.coords;\n const inGamut = r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1;\n const gamutDistance = inGamut ? 0 : Math.max(-r, r - 1, -g, g - 1, -b, b - 1, 0);\n return {\n r: Math.max(0, Math.min(255, Math.round(r * 255))),\n g: Math.max(0, Math.min(255, Math.round(g * 255))),\n b: Math.max(0, Math.min(255, Math.round(b * 255))),\n inGamut,\n gamutDistance,\n };\n}\n\n/**\n * Gamut-map an OKLCh color using the CSS gamut mapping algorithm, return hex.\n * Slower than oklchToSrgb/oklchToP3 (uses iterative reduction), use for single colors only.\n */\nexport function gamutMapOklchToHex(\n l: number,\n c: number,\n h: number,\n space: 'srgb' | 'p3' = 'srgb',\n): string {\n const col: PlainColorObject = { space: OKLCH_Space, coords: [l, c, h], alpha: 1 };\n const targetSpace = space === 'p3' ? P3_Space : sRGB_Space;\n const mapped = toGamutCSS(clone(col), { space: targetSpace });\n const rgb = to(mapped, sRGB_Space);\n return serialize(rgb, { format: 'hex' });\n}\n\n// Standard 11-step scale - will be transposed based on input anchors\nexport const STEP_NAMES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nexport type StepName = (typeof STEP_NAMES)[number];\n\nexport const COLOR_GROUPS = [\n 'neutral',\n 'brand',\n 'primary',\n 'accent',\n 'complement',\n 'success',\n 'warning',\n 'error',\n 'info',\n] as const;\nexport type ColorGroup = (typeof COLOR_GROUPS)[number];\n\n/**\n * Convert hex color to OKLCH components\n */\nexport function hexToOklch(hex: string): { l: number; c: number; h: number } {\n const color = new Color(hex);\n const oklch = color.to('oklch');\n return {\n l: oklch.coords[0],\n c: oklch.coords[1],\n h: oklch.coords[2] || 0,\n };\n}\n\nexport function oklchToHex(oklch: { l: number; c: number; h: number }): string {\n const color = new Color('oklch', [oklch.l, oklch.c, oklch.h]);\n const srgb = color.to('srgb');\n if (!srgb.inGamut()) {\n srgb.toGamut({ method: 'css' });\n }\n return srgb.toString({ format: 'hex' });\n}\n\n/**\n * Convert OKLCH CSS string to hex color\n * @example oklchStringToHex(\"oklch(50% 0.15 270)\") => \"#8b5cf6\"\n */\nexport function oklchStringToHex(oklchString: string): string {\n try {\n const color = new Color(oklchString);\n const srgb = color.to('srgb');\n if (!srgb.inGamut()) {\n srgb.toGamut({ method: 'css' });\n }\n return srgb.toString({ format: 'hex' });\n } catch {\n return '#808080';\n }\n}\n\n/**\n * Format a color name by separating compound words\n * Examples: \"mediumseagreen\" -> \"Medium Sea Green\", \"darkblue\" -> \"Dark Blue\"\n */\nexport function formatColorName(name: string): string {\n if (!name) return 'Unknown';\n\n const specialCases: Record<string, string> = {\n aliceblue: 'Alice Blue',\n antiquewhite: 'Antique White',\n blanchedalmond: 'Blanched Almond',\n blueviolet: 'Blue Violet',\n burlywood: 'Burly Wood',\n cadetblue: 'Cadet Blue',\n cornflowerblue: 'Cornflower Blue',\n darkblue: 'Dark Blue',\n darkcyan: 'Dark Cyan',\n darkgoldenrod: 'Dark Goldenrod',\n darkgray: 'Dark Gray',\n darkgreen: 'Dark Green',\n darkgrey: 'Dark Grey',\n darkkhaki: 'Dark Khaki',\n darkmagenta: 'Dark Magenta',\n darkolivegreen: 'Dark Olive Green',\n darkorange: 'Dark Orange',\n darkorchid: 'Dark Orchid',\n darkred: 'Dark Red',\n darksalmon: 'Dark Salmon',\n darkseagreen: 'Dark Sea Green',\n darkslateblue: 'Dark Slate Blue',\n darkslategray: 'Dark Slate Gray',\n darkslategrey: 'Dark Slate Grey',\n darkturquoise: 'Dark Turquoise',\n darkviolet: 'Dark Violet',\n deeppink: 'Deep Pink',\n deepskyblue: 'Deep Sky Blue',\n dimgray: 'Dim Gray',\n dimgrey: 'Dim Grey',\n dodgerblue: 'Dodger Blue',\n floralwhite: 'Floral White',\n forestgreen: 'Forest Green',\n ghostwhite: 'Ghost White',\n greenyellow: 'Green Yellow',\n hotpink: 'Hot Pink',\n indianred: 'Indian Red',\n lavenderblush: 'Lavender Blush',\n lawngreen: 'Lawn Green',\n lemonchiffon: 'Lemon Chiffon',\n lightblue: 'Light Blue',\n lightcoral: 'Light Coral',\n lightcyan: 'Light Cyan',\n lightgoldenrodyellow: 'Light Goldenrod Yellow',\n lightgray: 'Light Gray',\n lightgreen: 'Light Green',\n lightgrey: 'Light Grey',\n lightpink: 'Light Pink',\n lightsalmon: 'Light Salmon',\n lightseagreen: 'Light Sea Green',\n lightskyblue: 'Light Sky Blue',\n lightslategray: 'Light Slate Gray',\n lightslategrey: 'Light Slate Grey',\n lightsteelblue: 'Light Steel Blue',\n lightyellow: 'Light Yellow',\n limegreen: 'Lime Green',\n mediumaquamarine: 'Medium Aquamarine',\n mediumblue: 'Medium Blue',\n mediumorchid: 'Medium Orchid',\n mediumpurple: 'Medium Purple',\n mediumseagreen: 'Medium Sea Green',\n mediumslateblue: 'Medium Slate Blue',\n mediumspringgreen: 'Medium Spring Green',\n mediumturquoise: 'Medium Turquoise',\n mediumvioletred: 'Medium Violet Red',\n midnightblue: 'Midnight Blue',\n mintcream: 'Mint Cream',\n mistyrose: 'Misty Rose',\n navajowhite: 'Navajo White',\n oldlace: 'Old Lace',\n olivedrab: 'Olive Drab',\n orangered: 'Orange Red',\n palegoldenrod: 'Pale Goldenrod',\n palegreen: 'Pale Green',\n paleturquoise: 'Pale Turquoise',\n palevioletred: 'Pale Violet Red',\n papayawhip: 'Papaya Whip',\n peachpuff: 'Peach Puff',\n powderblue: 'Powder Blue',\n rosybrown: 'Rosy Brown',\n royalblue: 'Royal Blue',\n saddlebrown: 'Saddle Brown',\n sandybrown: 'Sandy Brown',\n seagreen: 'Sea Green',\n skyblue: 'Sky Blue',\n slateblue: 'Slate Blue',\n slategray: 'Slate Gray',\n slategrey: 'Slate Grey',\n springgreen: 'Spring Green',\n steelblue: 'Steel Blue',\n whitesmoke: 'White Smoke',\n yellowgreen: 'Yellow Green',\n };\n\n const lowerName = name.toLowerCase();\n if (specialCases[lowerName]) {\n return specialCases[lowerName];\n }\n\n // Default: just capitalize first letter\n return name.charAt(0).toUpperCase() + name.slice(1);\n}\n\n/**\n * Get the closest CSS color name for a hex color\n */\nexport function getColorName(hex: string): string {\n try {\n const color = new Color(hex);\n const oklch = color.to('oklch');\n const hue = oklch.coords[2] || 0;\n\n if (hue < 30 || hue >= 330) return 'Red';\n if (hue < 60) return 'Orange';\n if (hue < 90) return 'Yellow';\n if (hue < 150) return 'Green';\n if (hue < 210) return 'Cyan';\n if (hue < 270) return 'Blue';\n if (hue < 330) return 'Purple';\n return 'Unknown';\n } catch {\n return 'Unknown';\n }\n}\n\n/**\n * Get color name with hue value\n */\nexport function getColorNameWithHue(hex: string): string {\n try {\n const color = new Color(hex);\n const oklch = color.to('oklch');\n const hue = oklch.coords[2] || 0;\n const roundedHue = Math.round(hue / 10) * 10;\n const name = getColorName(hex);\n return `${name} ${roundedHue}`;\n } catch {\n return 'Unknown';\n }\n}\n\n// Color harmony definitions\nexport const HARMONY_TYPES = [\n 'single',\n 'analogous',\n 'triad',\n 'complementary',\n 'split-complementary',\n 'square',\n 'compound',\n] as const;\nexport type HarmonyType = (typeof HARMONY_TYPES)[number];\n\nexport interface HarmonyDefinition {\n label: string;\n offsets: number[];\n primaryIndex: number;\n}\n\nexport const HARMONY_DEFINITIONS: Record<HarmonyType, HarmonyDefinition> = {\n single: {\n label: 'Single',\n offsets: [0],\n primaryIndex: 0,\n },\n analogous: {\n label: 'Analogous',\n offsets: [-30, 0, 30],\n primaryIndex: 1,\n },\n triad: {\n label: 'Triad',\n offsets: [0, 120, 240],\n primaryIndex: 0,\n },\n complementary: {\n label: 'Complementary',\n offsets: [0, 180],\n primaryIndex: 0,\n },\n 'split-complementary': {\n label: 'Split Complementary',\n offsets: [0, 150, 210],\n primaryIndex: 0,\n },\n square: {\n label: 'Square',\n offsets: [0, 90, 180, 270],\n primaryIndex: 0,\n },\n compound: {\n label: 'Compound',\n offsets: [-30, 0, 180, 210],\n primaryIndex: 1,\n },\n};\n\n/**\n * The shared lightness ladder every ramp is built on (lightest → darkest).\n *\n * Fixed, NOT seed-derived. Two hues at the same step have to read equally light,\n * otherwise one contrast table cannot serve every hue and a `muted` variant means\n * something different depending on which role you ask. Only chroma and hue vary\n * between ramps.\n *\n * The previous engine transposed this curve onto each seed, which let relative\n * luminance at step 300 range from 0.253 (rust) to 0.384 (amber) across the\n * shipped palette — the drift this replaces.\n *\n * Anchored steps are the one deliberate exception: see `generateOklchPalette`.\n */\nexport const LIGHTNESS_LADDER: Record<StepName, number> = {\n 50: 0.98,\n 100: 0.95,\n 200: 0.9,\n 300: 0.83,\n 400: 0.74,\n 500: 0.65,\n 600: 0.54,\n 700: 0.47,\n 800: 0.38,\n 900: 0.29,\n 950: 0.21,\n};\n\n/**\n * Chroma a ramp decays *towards* beyond its outermost anchors, so the near-white\n * and near-black ends keep a whisper of the hue instead of going flat grey. The\n * dark end carries slightly more because chroma reads weaker against black.\n *\n * It is a CEILING, not a target — `Math.min(ENDPOINT_CHROMA.x, anchor.c)`. That\n * distinction is the whole reason a neutral is authorable. The interpolation\n * factor reaches exactly 1 at steps 50 and 950 for every anchor position, so as a\n * target it made the endpoints these constants *unconditionally*: seeds at chroma\n * 0.001, 0.002, 0.05 and 0.2 all produced a byte-identical step 50. A brand\n * wanting plain white with grey panels had no seed that worked, and chroma 0 was\n * worst of all — colorjs returns NaN hue for a true achromatic colour, which\n * `hexToOklch` collapses to 0, so the \"neutral\" came out pink.\n *\n * As a ceiling, a low-chroma seed simply stays low and a chroma-0 seed stays 0 at\n * every unanchored step, which makes the NaN-hue collapse invisible rather than\n * something `hexToOklch` has to solve (its `|| 0` is load-bearing for\n * `getColorName`).\n */\nexport const ENDPOINT_CHROMA = { light: 0.008, dark: 0.015 } as const;\n\ninterface OklchColor {\n l: number;\n c: number;\n h: number;\n}\n\n/**\n * Parses an OKLCH CSS string into numerical components.\n * @param oklchString - e.g., 'oklch(50% 0.15 270)'\n * @returns Lightness (0-1), Chroma (0-~0.37), Hue (0-360)\n */\nexport function parseOklch(oklchString: string): OklchColor {\n const parts = oklchString.match(/oklch\\(([^ ]+) ([^ ]+) ([^ ]+)\\)/);\n if (!parts) throw new Error('Invalid OKLCH string format.');\n\n const [, lRaw = '', cRaw = '', hRaw = ''] = parts;\n const l = parseFloat(lRaw) / (lRaw.includes('%') ? 100 : 1);\n const c = parseFloat(cRaw);\n const h = parseFloat(hRaw);\n\n return { l, c, h };\n}\n\n/**\n * Linearly interpolates a value between a start and end point.\n */\nfunction lerp(start: number, end: number, factor: number): number {\n return start + factor * (end - start);\n}\n\n/**\n * Interpolate hue the short way round the wheel. A ramp anchored at 350° and 10°\n * spans 20°, not 340° — the naive lerp sent it the long way through cyan.\n */\nfunction lerpHue(start: number, end: number, factor: number): number {\n const delta = ((((end - start) % 360) + 540) % 360) - 180;\n return (start + delta * factor + 360) % 360;\n}\n\n/**\n * The ladder step whose lightness is closest to `lightness` — i.e. where a\n * supplied colour naturally belongs.\n */\nexport function assignStepByLightness(lightness: number): StepName {\n let closestStep: StepName = 500;\n let minDiff = Math.abs(lightness - LIGHTNESS_LADDER[500]);\n\n for (const step of STEP_NAMES) {\n const diff = Math.abs(lightness - LIGHTNESS_LADDER[step]);\n if (diff < minDiff) {\n minDiff = diff;\n closestStep = step;\n }\n }\n\n return closestStep;\n}\n\n/**\n * How far an anchored colour sits from its step's ladder lightness.\n *\n * The generator surfaces this as a warning: a large deviation is legal (the\n * caller asked for that exact colour) but it means the hue will read heavier or\n * lighter than its siblings at that step, which is worth knowing rather than\n * discovering later.\n */\nexport function ladderDeviation(step: StepName, lightness: number): number {\n return lightness - LIGHTNESS_LADDER[step];\n}\n\n/** Absolute chroma ceiling — beyond this nothing survives gamut mapping anyway. */\nconst MAX_CHROMA = 0.37;\n\n/**\n * Build a complete 11-step OKLCH ramp from one or more anchor colours.\n *\n * **Ladder-first with exact anchors.** Every anchor is reproduced *verbatim* at\n * its step; every other step takes its lightness from `LIGHTNESS_LADDER`, with\n * chroma and hue interpolated between the flanking anchors and decayed toward\n * `ENDPOINT_CHROMA` beyond the outermost ones.\n *\n * The consequence is the point: a client's brand hex survives byte-for-byte,\n * while the other ten steps stay directly comparable to every other hue. The\n * deviation from the ladder is bounded and *local to anchored steps* — unlike\n * the previous engine, which warped the whole ramp to fit its anchors so every\n * step drifted.\n *\n * Chroma may come out beyond the sRGB gamut here; that is deliberate.\n * `oklchStringToHex` gamut-maps with the CSS algorithm, which reduces chroma\n * while holding lightness, so the ladder survives the conversion.\n */\nexport function generateOklchPalette(\n input: string | string[] | Record<string, string>,\n): Record<StepName, string> {\n let anchors: Record<number, string> = {};\n\n if (typeof input === 'string') {\n anchors[assignStepByLightness(parseOklch(input).l)] = input;\n } else if (Array.isArray(input)) {\n for (const colorStr of input) {\n anchors[assignStepByLightness(parseOklch(colorStr).l)] = colorStr;\n }\n } else if (typeof input === 'object' && input !== null) {\n anchors = { ...input };\n } else {\n throw new Error(\n 'Input must be a color string, array of colors, or object with step assignments',\n );\n }\n\n const parsedAnchors: Record<number, OklchColor> = {};\n for (const [key, value] of Object.entries(anchors)) {\n parsedAnchors[Number(key)] = parseOklch(value);\n }\n\n const anchorSteps = Object.keys(parsedAnchors)\n .map(Number)\n .sort((a, b) => a - b) as StepName[];\n\n // Interpolate across step *index*, not the numeric label: the labels aren't\n // evenly spaced (50→100 is a smaller jump than 100→200), so lerping on them\n // would bunch the ramp up at the light end.\n const idx = (step: number) => STEP_NAMES.indexOf(step as StepName);\n const lastIdx = STEP_NAMES.length - 1;\n\n const palette: Record<number, string> = {};\n\n for (const step of STEP_NAMES) {\n // An anchor is the caller's exact colour. Emit it untouched.\n if (parsedAnchors[step]) {\n palette[step] = anchors[step] as string;\n continue;\n }\n\n // `lighter` is the nearest anchor with a smaller step (50 is lightest);\n // `darker` the nearest with a larger one.\n const lighter = [...anchorSteps].reverse().find((s) => s < step);\n const darker = anchorSteps.find((s) => s > step);\n\n let chroma: number;\n let hue: number;\n\n if (lighter !== undefined && darker !== undefined) {\n const a = parsedAnchors[lighter] as OklchColor;\n const b = parsedAnchors[darker] as OklchColor;\n const factor = (idx(step) - idx(lighter)) / (idx(darker) - idx(lighter));\n chroma = lerp(a.c, b.c, factor);\n hue = lerpHue(a.h, b.h, factor);\n } else if (lighter !== undefined) {\n // Past the darkest anchor — fade toward the tinted near-black end.\n const a = parsedAnchors[lighter] as OklchColor;\n const factor = (idx(step) - idx(lighter)) / (lastIdx - idx(lighter));\n chroma = lerp(a.c, Math.min(ENDPOINT_CHROMA.dark, a.c), factor);\n hue = a.h;\n } else if (darker !== undefined) {\n // Past the lightest anchor — fade toward the tinted near-white end.\n const b = parsedAnchors[darker] as OklchColor;\n const factor = (idx(darker) - idx(step)) / idx(darker);\n chroma = lerp(b.c, Math.min(ENDPOINT_CHROMA.light, b.c), factor);\n hue = b.h;\n } else {\n chroma = 0.1;\n hue = 0;\n }\n\n const lightness = LIGHTNESS_LADDER[step];\n const finalL = (lightness * 100).toFixed(2) + '%';\n const finalC = Math.min(chroma, MAX_CHROMA).toFixed(4);\n const finalH = hue.toFixed(2);\n\n palette[step] = `oklch(${finalL} ${finalC} ${finalH})`;\n }\n\n return palette;\n}\n\n// ── Contrast + endpoints (for the functional-token layer) ────────────────────\n\n/** APCA lightness-contrast (Lc) magnitude between a foreground and background. */\nexport function contrastLc(fg: string, bg: string): number {\n return Math.abs(new Color(bg).contrast(fg, 'APCA'));\n}\n\n/**\n * Pick a tinted near-white or near-black foreground for best contrast on `bg`\n * (a whisper of the background's hue mixed into off-white/off-black).\n */\nexport function pickOn(bg: string, hue?: number): string {\n const h = hue ?? hexToOklch(bg).h;\n const white = oklchToHex({ l: 0.98, c: 0.01, h });\n const black = oklchToHex({ l: 0.16, c: 0.02, h });\n return contrastLc(white, bg) >= contrastLc(black, bg) ? white : black;\n}\n\n/**\n * The tinted off-white / off-black endpoints for a hue — white/black with a\n * whisper of the hue mixed in. These are the text/background tints every ramp\n * needs (and the one exception strict brand kits allow).\n *\n * Derived from `LIGHTNESS_LADDER` and `ENDPOINT_CHROMA` rather than its own\n * literals, so a strict tone kit's endpoints land on the same ladder rungs a\n * generated ramp would have used. This is the single definition — callers must\n * not hand-write their own near-white/near-black.\n */\nexport function tintedEndpoints(hue: number, chroma?: number): { light: string; dark: string } {\n return {\n light: oklchToHex({\n l: LIGHTNESS_LADDER[50],\n c: Math.min(chroma ?? ENDPOINT_CHROMA.light, 0.02),\n h: hue,\n }),\n dark: oklchToHex({\n l: LIGHTNESS_LADDER[950],\n c: Math.min((chroma ?? ENDPOINT_CHROMA.dark) * 1.5, 0.03),\n h: hue,\n }),\n };\n}\n","/**\n * Color harmony strategies for generating complementary colors\n */\n\nexport type HarmonyStrategy =\n | 'complement'\n | 'analogous-plus'\n | 'analogous-minus'\n | 'triadic-plus'\n | 'triadic-minus'\n | 'split-plus'\n | 'split-minus'\n | 'tetradic-plus'\n | 'tetradic-minus';\n\nexport interface HarmonyOption {\n id: HarmonyStrategy;\n label: string;\n description: string;\n hueShift: number;\n}\n\nexport const HARMONY_STRATEGIES: HarmonyOption[] = [\n {\n id: 'complement',\n label: 'Complement',\n description: 'Opposite on color wheel (high contrast)',\n hueShift: 180,\n },\n {\n id: 'analogous-plus',\n label: 'Analogous+',\n description: 'Adjacent colors (harmonious)',\n hueShift: 30,\n },\n {\n id: 'analogous-minus',\n label: 'Analogous-',\n description: 'Adjacent colors (harmonious)',\n hueShift: -30,\n },\n {\n id: 'triadic-plus',\n label: 'Triadic+',\n description: 'Evenly spaced (balanced)',\n hueShift: 120,\n },\n {\n id: 'triadic-minus',\n label: 'Triadic-',\n description: 'Evenly spaced (balanced)',\n hueShift: -120,\n },\n {\n id: 'split-plus',\n label: 'Split Complement+',\n description: 'Near opposite (softer contrast)',\n hueShift: 150,\n },\n {\n id: 'split-minus',\n label: 'Split Complement-',\n description: 'Near opposite (softer contrast)',\n hueShift: -150,\n },\n {\n id: 'tetradic-plus',\n label: 'Tetradic+',\n description: 'Square spacing (rich palette)',\n hueShift: 90,\n },\n {\n id: 'tetradic-minus',\n label: 'Tetradic-',\n description: 'Square spacing (rich palette)',\n hueShift: -90,\n },\n];\n\n/**\n * Applies a harmony strategy to a hue value\n */\nexport function applyHarmonyStrategy(baseHue: number, strategy: HarmonyStrategy): number {\n const harmonyOption = HARMONY_STRATEGIES.find((h) => h.id === strategy);\n if (!harmonyOption) return baseHue;\n\n let newHue = baseHue + harmonyOption.hueShift;\n\n // Normalize to 0-360 range\n while (newHue < 0) newHue += 360;\n while (newHue >= 360) newHue -= 360;\n\n return newHue;\n}\n\n/**\n * Gets all harmony suggestions for a given base hue\n */\nexport function getHarmonySuggestions(\n baseHue: number,\n): Array<{ strategy: HarmonyStrategy; hue: number; label: string; description: string }> {\n return HARMONY_STRATEGIES.map((strategy) => ({\n strategy: strategy.id,\n hue: applyHarmonyStrategy(baseHue, strategy.id),\n label: strategy.label,\n description: strategy.description,\n }));\n}\n\n/**\n * Default functional color hues (if user doesn't specify)\n */\nexport const DEFAULT_FUNCTIONAL_HUES = {\n success: 142, // Green\n warning: 38, // Orange/Amber\n error: 0, // Red\n info: 200, // Blue\n} as const;\n\n/**\n * Suggests a functional color hue based on existing palette to maintain harmony\n */\nexport function suggestFunctionalColorHue(\n type: keyof typeof DEFAULT_FUNCTIONAL_HUES,\n existingHues: number[],\n): number {\n const defaultHue = DEFAULT_FUNCTIONAL_HUES[type];\n\n if (existingHues.length === 0) {\n return defaultHue;\n }\n\n // Find the closest existing hue and adjust slightly to maintain harmony\n const avgHue = existingHues.reduce((sum, h) => sum + h, 0) / existingHues.length;\n\n // Rotate the default hue to be more in line with the palette's color temperature\n const hueShift = avgHue - 200; // Assuming 200 is neutral blue\n let adjustedHue = defaultHue + hueShift * 0.3; // 30% influence\n\n // Normalize\n while (adjustedHue < 0) adjustedHue += 360;\n while (adjustedHue >= 360) adjustedHue -= 360;\n\n return adjustedHue;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["OKLCH_Space","sRGB_Space","P3_Space"],"sources":["../../src/color/colors.ts","../../src/color/color-harmony.ts"],"sourcesContent":["import Color from 'colorjs.io';\nimport {\n OKLCH as OKLCH_Space,\n sRGB as sRGB_Space,\n P3 as P3_Space,\n ColorSpace,\n to,\n toGamutCSS,\n clone,\n serialize,\n type PlainColorObject,\n} from 'colorjs.io/fn';\n\nColorSpace.register(OKLCH_Space);\nColorSpace.register(sRGB_Space);\nColorSpace.register(P3_Space);\n\nexport interface RgbResult {\n r: number;\n g: number;\n b: number;\n inGamut: boolean;\n /** Max channel overshoot beyond [0,1] before clamping. 0 when in gamut. */\n gamutDistance: number;\n}\n\n/**\n * Convert OKLCh to sRGB, returning 0-255 clamped values with gamut flag.\n * Fast path for per-pixel canvas rendering (no gamut mapping, just clamp).\n */\nexport function oklchToSrgb(l: number, c: number, h: number): RgbResult {\n const col: PlainColorObject = { space: OKLCH_Space, coords: [l, c, h], alpha: 1 };\n const rgb = to(col, sRGB_Space);\n const [r, g, b] = rgb.coords;\n const inGamut = r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1;\n const gamutDistance = inGamut ? 0 : Math.max(-r, r - 1, -g, g - 1, -b, b - 1, 0);\n return {\n r: Math.max(0, Math.min(255, Math.round(r * 255))),\n g: Math.max(0, Math.min(255, Math.round(g * 255))),\n b: Math.max(0, Math.min(255, Math.round(b * 255))),\n inGamut,\n gamutDistance,\n };\n}\n\n/**\n * Convert OKLCh to Display P3, returning 0-255 clamped values with gamut flag.\n * Fast path for per-pixel canvas rendering on P3 displays.\n */\nexport function oklchToP3(l: number, c: number, h: number): RgbResult {\n const col: PlainColorObject = { space: OKLCH_Space, coords: [l, c, h], alpha: 1 };\n const rgb = to(col, P3_Space);\n const [r, g, b] = rgb.coords;\n const inGamut = r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1;\n const gamutDistance = inGamut ? 0 : Math.max(-r, r - 1, -g, g - 1, -b, b - 1, 0);\n return {\n r: Math.max(0, Math.min(255, Math.round(r * 255))),\n g: Math.max(0, Math.min(255, Math.round(g * 255))),\n b: Math.max(0, Math.min(255, Math.round(b * 255))),\n inGamut,\n gamutDistance,\n };\n}\n\n/**\n * Gamut-map an OKLCh color using the CSS gamut mapping algorithm, return hex.\n * Slower than oklchToSrgb/oklchToP3 (uses iterative reduction), use for single colors only.\n */\nexport function gamutMapOklchToHex(\n l: number,\n c: number,\n h: number,\n space: 'srgb' | 'p3' = 'srgb',\n): string {\n const col: PlainColorObject = { space: OKLCH_Space, coords: [l, c, h], alpha: 1 };\n const targetSpace = space === 'p3' ? P3_Space : sRGB_Space;\n const mapped = toGamutCSS(clone(col), { space: targetSpace });\n const rgb = to(mapped, sRGB_Space);\n return serialize(rgb, { format: 'hex' });\n}\n\n// Standard 11-step scale - will be transposed based on input anchors\nexport const STEP_NAMES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nexport type StepName = (typeof STEP_NAMES)[number];\n\nexport const COLOR_GROUPS = [\n 'neutral',\n 'brand',\n 'primary',\n 'accent',\n 'complement',\n 'success',\n 'warning',\n 'error',\n 'info',\n] as const;\nexport type ColorGroup = (typeof COLOR_GROUPS)[number];\n\n/**\n * Convert hex color to OKLCH components\n */\nexport function hexToOklch(hex: string): { l: number; c: number; h: number } {\n const color = new Color(hex);\n const oklch = color.to('oklch');\n return {\n l: oklch.coords[0],\n c: oklch.coords[1],\n h: oklch.coords[2] || 0,\n };\n}\n\nexport function oklchToHex(oklch: { l: number; c: number; h: number }): string {\n const color = new Color('oklch', [oklch.l, oklch.c, oklch.h]);\n const srgb = color.to('srgb');\n if (!srgb.inGamut()) {\n srgb.toGamut({ method: 'css' });\n }\n return srgb.toString({ format: 'hex' });\n}\n\n/**\n * Convert OKLCH CSS string to hex color\n * @example oklchStringToHex(\"oklch(50% 0.15 270)\") => \"#8b5cf6\"\n */\nexport function oklchStringToHex(oklchString: string): string {\n try {\n const color = new Color(oklchString);\n const srgb = color.to('srgb');\n if (!srgb.inGamut()) {\n srgb.toGamut({ method: 'css' });\n }\n return srgb.toString({ format: 'hex' });\n } catch {\n return '#808080';\n }\n}\n\n/**\n * Format a color name by separating compound words\n * Examples: \"mediumseagreen\" -> \"Medium Sea Green\", \"darkblue\" -> \"Dark Blue\"\n */\nexport function formatColorName(name: string): string {\n if (!name) return 'Unknown';\n\n const specialCases: Record<string, string> = {\n aliceblue: 'Alice Blue',\n antiquewhite: 'Antique White',\n blanchedalmond: 'Blanched Almond',\n blueviolet: 'Blue Violet',\n burlywood: 'Burly Wood',\n cadetblue: 'Cadet Blue',\n cornflowerblue: 'Cornflower Blue',\n darkblue: 'Dark Blue',\n darkcyan: 'Dark Cyan',\n darkgoldenrod: 'Dark Goldenrod',\n darkgray: 'Dark Gray',\n darkgreen: 'Dark Green',\n darkgrey: 'Dark Grey',\n darkkhaki: 'Dark Khaki',\n darkmagenta: 'Dark Magenta',\n darkolivegreen: 'Dark Olive Green',\n darkorange: 'Dark Orange',\n darkorchid: 'Dark Orchid',\n darkred: 'Dark Red',\n darksalmon: 'Dark Salmon',\n darkseagreen: 'Dark Sea Green',\n darkslateblue: 'Dark Slate Blue',\n darkslategray: 'Dark Slate Gray',\n darkslategrey: 'Dark Slate Grey',\n darkturquoise: 'Dark Turquoise',\n darkviolet: 'Dark Violet',\n deeppink: 'Deep Pink',\n deepskyblue: 'Deep Sky Blue',\n dimgray: 'Dim Gray',\n dimgrey: 'Dim Grey',\n dodgerblue: 'Dodger Blue',\n floralwhite: 'Floral White',\n forestgreen: 'Forest Green',\n ghostwhite: 'Ghost White',\n greenyellow: 'Green Yellow',\n hotpink: 'Hot Pink',\n indianred: 'Indian Red',\n lavenderblush: 'Lavender Blush',\n lawngreen: 'Lawn Green',\n lemonchiffon: 'Lemon Chiffon',\n lightblue: 'Light Blue',\n lightcoral: 'Light Coral',\n lightcyan: 'Light Cyan',\n lightgoldenrodyellow: 'Light Goldenrod Yellow',\n lightgray: 'Light Gray',\n lightgreen: 'Light Green',\n lightgrey: 'Light Grey',\n lightpink: 'Light Pink',\n lightsalmon: 'Light Salmon',\n lightseagreen: 'Light Sea Green',\n lightskyblue: 'Light Sky Blue',\n lightslategray: 'Light Slate Gray',\n lightslategrey: 'Light Slate Grey',\n lightsteelblue: 'Light Steel Blue',\n lightyellow: 'Light Yellow',\n limegreen: 'Lime Green',\n mediumaquamarine: 'Medium Aquamarine',\n mediumblue: 'Medium Blue',\n mediumorchid: 'Medium Orchid',\n mediumpurple: 'Medium Purple',\n mediumseagreen: 'Medium Sea Green',\n mediumslateblue: 'Medium Slate Blue',\n mediumspringgreen: 'Medium Spring Green',\n mediumturquoise: 'Medium Turquoise',\n mediumvioletred: 'Medium Violet Red',\n midnightblue: 'Midnight Blue',\n mintcream: 'Mint Cream',\n mistyrose: 'Misty Rose',\n navajowhite: 'Navajo White',\n oldlace: 'Old Lace',\n olivedrab: 'Olive Drab',\n orangered: 'Orange Red',\n palegoldenrod: 'Pale Goldenrod',\n palegreen: 'Pale Green',\n paleturquoise: 'Pale Turquoise',\n palevioletred: 'Pale Violet Red',\n papayawhip: 'Papaya Whip',\n peachpuff: 'Peach Puff',\n powderblue: 'Powder Blue',\n rosybrown: 'Rosy Brown',\n royalblue: 'Royal Blue',\n saddlebrown: 'Saddle Brown',\n sandybrown: 'Sandy Brown',\n seagreen: 'Sea Green',\n skyblue: 'Sky Blue',\n slateblue: 'Slate Blue',\n slategray: 'Slate Gray',\n slategrey: 'Slate Grey',\n springgreen: 'Spring Green',\n steelblue: 'Steel Blue',\n whitesmoke: 'White Smoke',\n yellowgreen: 'Yellow Green',\n };\n\n const lowerName = name.toLowerCase();\n if (specialCases[lowerName]) {\n return specialCases[lowerName];\n }\n\n // Default: just capitalize first letter\n return name.charAt(0).toUpperCase() + name.slice(1);\n}\n\n/**\n * Get the closest CSS color name for a hex color\n */\nexport function getColorName(hex: string): string {\n try {\n const color = new Color(hex);\n const oklch = color.to('oklch');\n const hue = oklch.coords[2] || 0;\n\n if (hue < 30 || hue >= 330) return 'Red';\n if (hue < 60) return 'Orange';\n if (hue < 90) return 'Yellow';\n if (hue < 150) return 'Green';\n if (hue < 210) return 'Cyan';\n if (hue < 270) return 'Blue';\n if (hue < 330) return 'Purple';\n return 'Unknown';\n } catch {\n return 'Unknown';\n }\n}\n\n/**\n * Get color name with hue value\n */\nexport function getColorNameWithHue(hex: string): string {\n try {\n const color = new Color(hex);\n const oklch = color.to('oklch');\n const hue = oklch.coords[2] || 0;\n const roundedHue = Math.round(hue / 10) * 10;\n const name = getColorName(hex);\n return `${name} ${roundedHue}`;\n } catch {\n return 'Unknown';\n }\n}\n\n// Color harmony definitions\nexport const HARMONY_TYPES = [\n 'single',\n 'analogous',\n 'triad',\n 'complementary',\n 'split-complementary',\n 'square',\n 'compound',\n] as const;\nexport type HarmonyType = (typeof HARMONY_TYPES)[number];\n\nexport interface HarmonyDefinition {\n label: string;\n offsets: number[];\n primaryIndex: number;\n}\n\nexport const HARMONY_DEFINITIONS: Record<HarmonyType, HarmonyDefinition> = {\n single: {\n label: 'Single',\n offsets: [0],\n primaryIndex: 0,\n },\n analogous: {\n label: 'Analogous',\n offsets: [-30, 0, 30],\n primaryIndex: 1,\n },\n triad: {\n label: 'Triad',\n offsets: [0, 120, 240],\n primaryIndex: 0,\n },\n complementary: {\n label: 'Complementary',\n offsets: [0, 180],\n primaryIndex: 0,\n },\n 'split-complementary': {\n label: 'Split Complementary',\n offsets: [0, 150, 210],\n primaryIndex: 0,\n },\n square: {\n label: 'Square',\n offsets: [0, 90, 180, 270],\n primaryIndex: 0,\n },\n compound: {\n label: 'Compound',\n offsets: [-30, 0, 180, 210],\n primaryIndex: 1,\n },\n};\n\n/**\n * The shared lightness ladder every ramp is built on (lightest → darkest).\n *\n * Fixed, NOT seed-derived. Two hues at the same step have to read equally light,\n * otherwise one contrast table cannot serve every hue and a `muted` variant means\n * something different depending on which role you ask. Only chroma and hue vary\n * between ramps.\n *\n * The previous engine transposed this curve onto each seed, which let relative\n * luminance at step 300 range from 0.253 (rust) to 0.384 (amber) across the\n * shipped palette — the drift this replaces.\n *\n * Anchored steps are the one deliberate exception: see `generateOklchPalette`.\n */\nexport const LIGHTNESS_LADDER: Record<StepName, number> = {\n 50: 0.98,\n 100: 0.95,\n 200: 0.9,\n 300: 0.83,\n 400: 0.74,\n 500: 0.65,\n 600: 0.54,\n 700: 0.47,\n 800: 0.38,\n 900: 0.29,\n 950: 0.21,\n};\n\n/**\n * Chroma a ramp decays *towards* beyond its outermost anchors, so the near-white\n * and near-black ends keep a whisper of the hue instead of going flat grey. The\n * dark end carries slightly more because chroma reads weaker against black.\n *\n * It is a CEILING, not a target — `Math.min(ENDPOINT_CHROMA.x, anchor.c)`. That\n * distinction is the whole reason a neutral is authorable. The interpolation\n * factor reaches exactly 1 at steps 50 and 950 for every anchor position, so as a\n * target it made the endpoints these constants *unconditionally*: seeds at chroma\n * 0.001, 0.002, 0.05 and 0.2 all produced a byte-identical step 50. A brand\n * wanting plain white with grey panels had no seed that worked, and chroma 0 was\n * worst of all — colorjs returns NaN hue for a true achromatic colour, which\n * `hexToOklch` collapses to 0, so the \"neutral\" came out pink.\n *\n * As a ceiling, a low-chroma seed simply stays low and a chroma-0 seed stays 0 at\n * every unanchored step, which makes the NaN-hue collapse invisible rather than\n * something `hexToOklch` has to solve (its `|| 0` is load-bearing for\n * `getColorName`).\n */\nexport const ENDPOINT_CHROMA = { light: 0.008, dark: 0.015 } as const;\n\ninterface OklchColor {\n l: number;\n c: number;\n h: number;\n}\n\n/**\n * Parses an OKLCH CSS string into numerical components.\n * @param oklchString - e.g., 'oklch(50% 0.15 270)'\n * @returns Lightness (0-1), Chroma (0-~0.37), Hue (0-360)\n */\nexport function parseOklch(oklchString: string): OklchColor {\n const parts = oklchString.match(/oklch\\(([^ ]+) ([^ ]+) ([^ ]+)\\)/);\n if (!parts) throw new Error('Invalid OKLCH string format.');\n\n const [, lRaw = '', cRaw = '', hRaw = ''] = parts;\n const l = parseFloat(lRaw) / (lRaw.includes('%') ? 100 : 1);\n const c = parseFloat(cRaw);\n const h = parseFloat(hRaw);\n\n return { l, c, h };\n}\n\n/**\n * Linearly interpolates a value between a start and end point.\n */\nfunction lerp(start: number, end: number, factor: number): number {\n return start + factor * (end - start);\n}\n\n/**\n * Interpolate hue the short way round the wheel. A ramp anchored at 350° and 10°\n * spans 20°, not 340° — the naive lerp sent it the long way through cyan.\n */\nfunction lerpHue(start: number, end: number, factor: number): number {\n const delta = ((((end - start) % 360) + 540) % 360) - 180;\n return (start + delta * factor + 360) % 360;\n}\n\n/**\n * The ladder step whose lightness is closest to `lightness` — i.e. where a\n * supplied colour naturally belongs.\n */\nexport function assignStepByLightness(lightness: number): StepName {\n let closestStep: StepName = 500;\n let minDiff = Math.abs(lightness - LIGHTNESS_LADDER[500]);\n\n for (const step of STEP_NAMES) {\n const diff = Math.abs(lightness - LIGHTNESS_LADDER[step]);\n if (diff < minDiff) {\n minDiff = diff;\n closestStep = step;\n }\n }\n\n return closestStep;\n}\n\n/**\n * How far an anchored colour sits from its step's ladder lightness.\n *\n * The generator surfaces this as a warning: a large deviation is legal (the\n * caller asked for that exact colour) but it means the hue will read heavier or\n * lighter than its siblings at that step, which is worth knowing rather than\n * discovering later.\n */\nexport function ladderDeviation(step: StepName, lightness: number): number {\n return lightness - LIGHTNESS_LADDER[step];\n}\n\n/** Absolute chroma ceiling — beyond this nothing survives gamut mapping anyway. */\nconst MAX_CHROMA = 0.37;\n\n/**\n * Build a complete 11-step OKLCH ramp from one or more anchor colours.\n *\n * **Ladder-first with exact anchors.** Every anchor is reproduced *verbatim* at\n * its step; every other step takes its lightness from `LIGHTNESS_LADDER`, with\n * chroma and hue interpolated between the flanking anchors and decayed toward\n * `ENDPOINT_CHROMA` beyond the outermost ones.\n *\n * The consequence is the point: a client's brand hex survives byte-for-byte,\n * while the other ten steps stay directly comparable to every other hue. The\n * deviation from the ladder is bounded and *local to anchored steps* — unlike\n * the previous engine, which warped the whole ramp to fit its anchors so every\n * step drifted.\n *\n * Chroma may come out beyond the sRGB gamut here; that is deliberate.\n * `oklchStringToHex` gamut-maps with the CSS algorithm, which reduces chroma\n * while holding lightness, so the ladder survives the conversion.\n */\nexport function generateOklchPalette(\n input: string | string[] | Record<string, string>,\n): Record<StepName, string> {\n let anchors: Record<number, string> = {};\n\n if (typeof input === 'string') {\n anchors[assignStepByLightness(parseOklch(input).l)] = input;\n } else if (Array.isArray(input)) {\n for (const colorStr of input) {\n anchors[assignStepByLightness(parseOklch(colorStr).l)] = colorStr;\n }\n } else if (typeof input === 'object' && input !== null) {\n anchors = { ...input };\n } else {\n throw new Error(\n 'Input must be a color string, array of colors, or object with step assignments',\n );\n }\n\n const parsedAnchors: Record<number, OklchColor> = {};\n for (const [key, value] of Object.entries(anchors)) {\n parsedAnchors[Number(key)] = parseOklch(value);\n }\n\n const anchorSteps = Object.keys(parsedAnchors)\n .map(Number)\n .sort((a, b) => a - b) as StepName[];\n\n // Interpolate across step *index*, not the numeric label: the labels aren't\n // evenly spaced (50→100 is a smaller jump than 100→200), so lerping on them\n // would bunch the ramp up at the light end.\n const idx = (step: number) => STEP_NAMES.indexOf(step as StepName);\n const lastIdx = STEP_NAMES.length - 1;\n\n const palette: Record<number, string> = {};\n\n for (const step of STEP_NAMES) {\n // An anchor is the caller's exact colour. Emit it untouched.\n if (parsedAnchors[step]) {\n palette[step] = anchors[step] as string;\n continue;\n }\n\n // `lighter` is the nearest anchor with a smaller step (50 is lightest);\n // `darker` the nearest with a larger one.\n const lighter = [...anchorSteps].reverse().find((s) => s < step);\n const darker = anchorSteps.find((s) => s > step);\n\n let chroma: number;\n let hue: number;\n\n if (lighter !== undefined && darker !== undefined) {\n const a = parsedAnchors[lighter] as OklchColor;\n const b = parsedAnchors[darker] as OklchColor;\n const factor = (idx(step) - idx(lighter)) / (idx(darker) - idx(lighter));\n chroma = lerp(a.c, b.c, factor);\n hue = lerpHue(a.h, b.h, factor);\n } else if (lighter !== undefined) {\n // Past the darkest anchor — fade toward the tinted near-black end.\n const a = parsedAnchors[lighter] as OklchColor;\n const factor = (idx(step) - idx(lighter)) / (lastIdx - idx(lighter));\n chroma = lerp(a.c, Math.min(ENDPOINT_CHROMA.dark, a.c), factor);\n hue = a.h;\n } else if (darker !== undefined) {\n // Past the lightest anchor — fade toward the tinted near-white end.\n const b = parsedAnchors[darker] as OklchColor;\n const factor = (idx(darker) - idx(step)) / idx(darker);\n chroma = lerp(b.c, Math.min(ENDPOINT_CHROMA.light, b.c), factor);\n hue = b.h;\n } else {\n chroma = 0.1;\n hue = 0;\n }\n\n const lightness = LIGHTNESS_LADDER[step];\n const finalL = (lightness * 100).toFixed(2) + '%';\n const finalC = Math.min(chroma, MAX_CHROMA).toFixed(4);\n const finalH = hue.toFixed(2);\n\n palette[step] = `oklch(${finalL} ${finalC} ${finalH})`;\n }\n\n return palette;\n}\n\n// ── Contrast + endpoints (for the functional-token layer) ────────────────────\n\n/** APCA lightness-contrast (Lc) magnitude between a foreground and background. */\nexport function contrastLc(fg: string, bg: string): number {\n return Math.abs(new Color(bg).contrast(fg, 'APCA'));\n}\n\n/**\n * Pick a tinted near-white or near-black foreground for best contrast on `bg`\n * (a whisper of the background's hue mixed into off-white/off-black).\n *\n * `prefer` forces the polarity instead of choosing the better one — for a role\n * whose author wants dark text on a bright fill (or the reverse). Whether that\n * pairing is legible is the caller's contrast check to decide, not this\n * function's.\n */\nexport function pickOn(bg: string, hue?: number, prefer?: 'light' | 'dark'): string {\n const h = hue ?? hexToOklch(bg).h;\n const white = oklchToHex({ l: 0.98, c: 0.01, h });\n const black = oklchToHex({ l: 0.16, c: 0.02, h });\n if (prefer === 'light') return white;\n if (prefer === 'dark') return black;\n return contrastLc(white, bg) >= contrastLc(black, bg) ? white : black;\n}\n\n/**\n * The tinted off-white / off-black endpoints for a hue — white/black with a\n * whisper of the hue mixed in. These are the text/background tints every ramp\n * needs (and the one exception strict brand kits allow).\n *\n * Derived from `LIGHTNESS_LADDER` and `ENDPOINT_CHROMA` rather than its own\n * literals, so a strict tone kit's endpoints land on the same ladder rungs a\n * generated ramp would have used. This is the single definition — callers must\n * not hand-write their own near-white/near-black.\n */\nexport function tintedEndpoints(hue: number, chroma?: number): { light: string; dark: string } {\n return {\n light: oklchToHex({\n l: LIGHTNESS_LADDER[50],\n c: Math.min(chroma ?? ENDPOINT_CHROMA.light, 0.02),\n h: hue,\n }),\n dark: oklchToHex({\n l: LIGHTNESS_LADDER[950],\n c: Math.min((chroma ?? ENDPOINT_CHROMA.dark) * 1.5, 0.03),\n h: hue,\n }),\n };\n}\n","/**\n * Color harmony strategies for generating complementary colors\n */\n\nexport type HarmonyStrategy =\n | 'complement'\n | 'analogous-plus'\n | 'analogous-minus'\n | 'triadic-plus'\n | 'triadic-minus'\n | 'split-plus'\n | 'split-minus'\n | 'tetradic-plus'\n | 'tetradic-minus';\n\nexport interface HarmonyOption {\n id: HarmonyStrategy;\n label: string;\n description: string;\n hueShift: number;\n}\n\nexport const HARMONY_STRATEGIES: HarmonyOption[] = [\n {\n id: 'complement',\n label: 'Complement',\n description: 'Opposite on color wheel (high contrast)',\n hueShift: 180,\n },\n {\n id: 'analogous-plus',\n label: 'Analogous+',\n description: 'Adjacent colors (harmonious)',\n hueShift: 30,\n },\n {\n id: 'analogous-minus',\n label: 'Analogous-',\n description: 'Adjacent colors (harmonious)',\n hueShift: -30,\n },\n {\n id: 'triadic-plus',\n label: 'Triadic+',\n description: 'Evenly spaced (balanced)',\n hueShift: 120,\n },\n {\n id: 'triadic-minus',\n label: 'Triadic-',\n description: 'Evenly spaced (balanced)',\n hueShift: -120,\n },\n {\n id: 'split-plus',\n label: 'Split Complement+',\n description: 'Near opposite (softer contrast)',\n hueShift: 150,\n },\n {\n id: 'split-minus',\n label: 'Split Complement-',\n description: 'Near opposite (softer contrast)',\n hueShift: -150,\n },\n {\n id: 'tetradic-plus',\n label: 'Tetradic+',\n description: 'Square spacing (rich palette)',\n hueShift: 90,\n },\n {\n id: 'tetradic-minus',\n label: 'Tetradic-',\n description: 'Square spacing (rich palette)',\n hueShift: -90,\n },\n];\n\n/**\n * Applies a harmony strategy to a hue value\n */\nexport function applyHarmonyStrategy(baseHue: number, strategy: HarmonyStrategy): number {\n const harmonyOption = HARMONY_STRATEGIES.find((h) => h.id === strategy);\n if (!harmonyOption) return baseHue;\n\n let newHue = baseHue + harmonyOption.hueShift;\n\n // Normalize to 0-360 range\n while (newHue < 0) newHue += 360;\n while (newHue >= 360) newHue -= 360;\n\n return newHue;\n}\n\n/**\n * Gets all harmony suggestions for a given base hue\n */\nexport function getHarmonySuggestions(\n baseHue: number,\n): Array<{ strategy: HarmonyStrategy; hue: number; label: string; description: string }> {\n return HARMONY_STRATEGIES.map((strategy) => ({\n strategy: strategy.id,\n hue: applyHarmonyStrategy(baseHue, strategy.id),\n label: strategy.label,\n description: strategy.description,\n }));\n}\n\n/**\n * Default functional color hues (if user doesn't specify)\n */\nexport const DEFAULT_FUNCTIONAL_HUES = {\n success: 142, // Green\n warning: 38, // Orange/Amber\n error: 0, // Red\n info: 200, // Blue\n} as const;\n\n/**\n * Suggests a functional color hue based on existing palette to maintain harmony\n */\nexport function suggestFunctionalColorHue(\n type: keyof typeof DEFAULT_FUNCTIONAL_HUES,\n existingHues: number[],\n): number {\n const defaultHue = DEFAULT_FUNCTIONAL_HUES[type];\n\n if (existingHues.length === 0) {\n return defaultHue;\n }\n\n // Find the closest existing hue and adjust slightly to maintain harmony\n const avgHue = existingHues.reduce((sum, h) => sum + h, 0) / existingHues.length;\n\n // Rotate the default hue to be more in line with the palette's color temperature\n const hueShift = avgHue - 200; // Assuming 200 is neutral blue\n let adjustedHue = defaultHue + hueShift * 0.3; // 30% influence\n\n // Normalize\n while (adjustedHue < 0) adjustedHue += 360;\n while (adjustedHue >= 360) adjustedHue -= 360;\n\n return adjustedHue;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic content-model types, guards, i18n, and part-attr resolution.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from @getvitops/astro so the pure-TS helpers live in one place (no
|
|
5
|
+
* Astro dependency). The Astro-typed element tag was `astro/types`' `HTMLTag`;
|
|
6
|
+
* here it's a plain `string` (any tag name), keeping these fully framework-neutral.
|
|
7
|
+
*/
|
|
8
|
+
/** A translatable value: a plain string, or a locale → string map. */
|
|
9
|
+
export type Localizable = string | Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* An HTML attribute bag. `class` may be a string or a class:list-style array
|
|
12
|
+
* (falsy entries dropped); everything else is passed through to the element.
|
|
13
|
+
*/
|
|
14
|
+
export interface Attrs {
|
|
15
|
+
class?: string | (string | false | null | undefined)[];
|
|
16
|
+
[attr: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** An image reference used by image links. */
|
|
19
|
+
export interface ImageRef {
|
|
20
|
+
src: string;
|
|
21
|
+
alt?: Localizable;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
/** A link with a text label. */
|
|
25
|
+
export interface TextLink {
|
|
26
|
+
label: Localizable;
|
|
27
|
+
href: string;
|
|
28
|
+
attrs?: Attrs;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
/** A link whose content is an image. */
|
|
32
|
+
export interface ImageLink {
|
|
33
|
+
image: ImageRef;
|
|
34
|
+
href: string;
|
|
35
|
+
attrs?: Attrs;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/** Any link — text or image. */
|
|
39
|
+
export type Link = TextLink | ImageLink;
|
|
40
|
+
/**
|
|
41
|
+
* A generic content element wrapper (discriminated by `type: 'element'`),
|
|
42
|
+
* carrying a payload `T` rendered as `Tag` (any HTML tag name).
|
|
43
|
+
*/
|
|
44
|
+
export interface Elmnt<T = unknown, Tag extends string = 'div'> {
|
|
45
|
+
type: 'element';
|
|
46
|
+
tag?: Tag;
|
|
47
|
+
attrs?: Attrs;
|
|
48
|
+
content?: T;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A renderable node tree — a text string (Localizable) or an element with
|
|
53
|
+
* optional attrs and either `text` shorthand or `children`.
|
|
54
|
+
*/
|
|
55
|
+
export type ContentNode = string | {
|
|
56
|
+
tag: string;
|
|
57
|
+
attrs?: Attrs;
|
|
58
|
+
text?: Localizable;
|
|
59
|
+
children?: ContentNode[];
|
|
60
|
+
};
|
|
61
|
+
export declare function isElmnt<T, Tag extends string = 'div'>(item: any): item is Elmnt<T, Tag>;
|
|
62
|
+
export declare function isLink(item: any): item is Link;
|
|
63
|
+
export declare function isTextLink(item: any): item is TextLink;
|
|
64
|
+
export declare function isImageLink(item: any): item is ImageLink;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a Localizable value to a string for the given locale. A plain string
|
|
67
|
+
* is returned as-is; a locale map returns the best match (locale → default → '').
|
|
68
|
+
*/
|
|
69
|
+
export declare function t(value: Localizable | undefined, locale: string, defaultLocale: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve per-part attrs from a presentation-type config. For the `root` part,
|
|
72
|
+
* merges legacy `attrs` with `parts.root` (backward compat).
|
|
73
|
+
*/
|
|
74
|
+
export declare function partAttrs(present: {
|
|
75
|
+
attrs?: Attrs;
|
|
76
|
+
parts?: Record<string, Attrs>;
|
|
77
|
+
} | undefined, part: string): Attrs;
|
|
78
|
+
//# sourceMappingURL=content.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.mts","names":[],"sources":["../src/content.ts"],"mappings":";;;;;;;;YAWY,uBAAuB;;;;;iBAMlB;EACf;GACC;;;iBAIc;EACf;EACA,MAAM;GACL;;;iBAIc;EACf,OAAO;EACP;EACA,QAAQ;GACP;;;iBAIc;EACf,OAAO;EACP;EACA,QAAQ;GACP;;;YAIS,OAAO,WAAW;;;;;iBAMb,MAAM,aAAa;EAClC;EACA,MAAM;EACN,QAAQ;EACR,UAAU;GACT;;;;;;YAOS;EAGN;EACA,QAAQ;EACR,OAAO;EACP,WAAW;;wBAKD,QAAQ,GAAG,4BAA4B,YAAY,QAAQ,MAAM,GAAG;wBAIpE,OAAO,YAAY,QAAQ;wBAI3B,WAAW,YAAY,QAAQ;wBAI/B,YAAY,YAAY,QAAQ;;;;;wBAUhC,EAAE,OAAO,yBAAyB,gBAAgB;;;;;wBAYlD,UACd;EAAW,QAAQ;EAAO,QAAQ,eAAe;eACjD,eACC"}
|
package/dist/content.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/content.ts
|
|
2
|
+
function isElmnt(item) {
|
|
3
|
+
return item && item.type === "element";
|
|
4
|
+
}
|
|
5
|
+
function isLink(item) {
|
|
6
|
+
return item && ("label" in item || "image" in item) && "href" in item;
|
|
7
|
+
}
|
|
8
|
+
function isTextLink(item) {
|
|
9
|
+
return item && "label" in item && "href" in item;
|
|
10
|
+
}
|
|
11
|
+
function isImageLink(item) {
|
|
12
|
+
return item && "image" in item && "href" in item;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a Localizable value to a string for the given locale. A plain string
|
|
16
|
+
* is returned as-is; a locale map returns the best match (locale → default → '').
|
|
17
|
+
*/
|
|
18
|
+
function t(value, locale, defaultLocale) {
|
|
19
|
+
if (value == null) return "";
|
|
20
|
+
if (typeof value === "string") return value;
|
|
21
|
+
return value[locale] ?? value[defaultLocale] ?? "";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve per-part attrs from a presentation-type config. For the `root` part,
|
|
25
|
+
* merges legacy `attrs` with `parts.root` (backward compat).
|
|
26
|
+
*/
|
|
27
|
+
function partAttrs(present, part) {
|
|
28
|
+
const fromParts = present?.parts?.[part];
|
|
29
|
+
if (part === "root") {
|
|
30
|
+
const legacy = present?.attrs;
|
|
31
|
+
if (!legacy && !fromParts) return {};
|
|
32
|
+
const { class: lc, ...lr } = legacy ?? {};
|
|
33
|
+
const { class: pc, ...pr } = fromParts ?? {};
|
|
34
|
+
const classes = [lc, pc].flat().filter((c) => typeof c === "string" && c !== "");
|
|
35
|
+
return {
|
|
36
|
+
...classes.length ? { class: classes } : {},
|
|
37
|
+
...lr,
|
|
38
|
+
...pr
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return fromParts ?? {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { isElmnt, isImageLink, isLink, isTextLink, partAttrs, t };
|
|
45
|
+
//# sourceMappingURL=content.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.mjs","names":[],"sources":["../src/content.ts"],"sourcesContent":["/**\n * Framework-agnostic content-model types, guards, i18n, and part-attr resolution.\n *\n * Extracted from @getvitops/astro so the pure-TS helpers live in one place (no\n * Astro dependency). The Astro-typed element tag was `astro/types`' `HTMLTag`;\n * here it's a plain `string` (any tag name), keeping these fully framework-neutral.\n */\n\n// ── content-model types ──────────────────────────────────────────────────────\n\n/** A translatable value: a plain string, or a locale → string map. */\nexport type Localizable = string | Record<string, string>;\n\n/**\n * An HTML attribute bag. `class` may be a string or a class:list-style array\n * (falsy entries dropped); everything else is passed through to the element.\n */\nexport interface Attrs {\n class?: string | (string | false | null | undefined)[];\n [attr: string]: unknown;\n}\n\n/** An image reference used by image links. */\nexport interface ImageRef {\n src: string;\n alt?: Localizable;\n [key: string]: unknown;\n}\n\n/** A link with a text label. */\nexport interface TextLink {\n label: Localizable;\n href: string;\n attrs?: Attrs;\n [key: string]: unknown;\n}\n\n/** A link whose content is an image. */\nexport interface ImageLink {\n image: ImageRef;\n href: string;\n attrs?: Attrs;\n [key: string]: unknown;\n}\n\n/** Any link — text or image. */\nexport type Link = TextLink | ImageLink;\n\n/**\n * A generic content element wrapper (discriminated by `type: 'element'`),\n * carrying a payload `T` rendered as `Tag` (any HTML tag name).\n */\nexport interface Elmnt<T = unknown, Tag extends string = 'div'> {\n type: 'element';\n tag?: Tag;\n attrs?: Attrs;\n content?: T;\n [key: string]: unknown;\n}\n\n/**\n * A renderable node tree — a text string (Localizable) or an element with\n * optional attrs and either `text` shorthand or `children`.\n */\nexport type ContentNode =\n | string\n | {\n tag: string;\n attrs?: Attrs;\n text?: Localizable;\n children?: ContentNode[];\n };\n\n// ── type guards ──────────────────────────────────────────────────────────────\n\nexport function isElmnt<T, Tag extends string = 'div'>(item: any): item is Elmnt<T, Tag> {\n return item && item.type === 'element';\n}\n\nexport function isLink(item: any): item is Link {\n return item && ('label' in item || 'image' in item) && 'href' in item;\n}\n\nexport function isTextLink(item: any): item is TextLink {\n return item && 'label' in item && 'href' in item;\n}\n\nexport function isImageLink(item: any): item is ImageLink {\n return item && 'image' in item && 'href' in item;\n}\n\n// ── i18n ─────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve a Localizable value to a string for the given locale. A plain string\n * is returned as-is; a locale map returns the best match (locale → default → '').\n */\nexport function t(value: Localizable | undefined, locale: string, defaultLocale: string): string {\n if (value == null) return '';\n if (typeof value === 'string') return value;\n return value[locale] ?? value[defaultLocale] ?? '';\n}\n\n// ── part-attr resolution ─────────────────────────────────────────────────────\n\n/**\n * Resolve per-part attrs from a presentation-type config. For the `root` part,\n * merges legacy `attrs` with `parts.root` (backward compat).\n */\nexport function partAttrs(\n present: { attrs?: Attrs; parts?: Record<string, Attrs> } | undefined,\n part: string,\n): Attrs {\n const fromParts = present?.parts?.[part];\n if (part === 'root') {\n const legacy = present?.attrs;\n if (!legacy && !fromParts) return {};\n const { class: lc, ...lr } = legacy ?? {};\n const { class: pc, ...pr } = fromParts ?? {};\n const classes = [lc, pc].flat().filter((c): c is string => typeof c === 'string' && c !== '');\n return {\n ...(classes.length ? { class: classes } : {}),\n ...lr,\n ...pr,\n };\n }\n return fromParts ?? {};\n}\n"],"mappings":";AA2EA,SAAgB,QAAuC,MAAkC;CACvF,OAAO,QAAQ,KAAK,SAAS;AAC/B;AAEA,SAAgB,OAAO,MAAyB;CAC9C,OAAO,SAAS,WAAW,QAAQ,WAAW,SAAS,UAAU;AACnE;AAEA,SAAgB,WAAW,MAA6B;CACtD,OAAO,QAAQ,WAAW,QAAQ,UAAU;AAC9C;AAEA,SAAgB,YAAY,MAA8B;CACxD,OAAO,QAAQ,WAAW,QAAQ,UAAU;AAC9C;;;;;AAQA,SAAgB,EAAE,OAAgC,QAAgB,eAA+B;CAC/F,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,OAAO,MAAM,WAAW,MAAM,kBAAkB;AAClD;;;;;AAQA,SAAgB,UACd,SACA,MACO;CACP,MAAM,YAAY,SAAS,QAAQ;CACnC,IAAI,SAAS,QAAQ;EACnB,MAAM,SAAS,SAAS;EACxB,IAAI,CAAC,UAAU,CAAC,WAAW,OAAO,CAAC;EACnC,MAAM,EAAE,OAAO,IAAI,GAAG,OAAO,UAAU,CAAC;EACxC,MAAM,EAAE,OAAO,IAAI,GAAG,OAAO,aAAa,CAAC;EAC3C,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,MAAmB,OAAO,MAAM,YAAY,MAAM,EAAE;EAC5F,OAAO;GACL,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,IAAI,CAAC;GAC3C,GAAG;GACH,GAAG;EACL;CACF;CACA,OAAO,aAAa,CAAC;AACvB"}
|
package/dist/crm/index.d.mts
CHANGED
|
@@ -1,79 +1,14 @@
|
|
|
1
|
-
import { t as ConversionEvent } from "../types-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* Deliberately structural rather than `Config['site']['crm']` imported from
|
|
7
|
-
* `@getvitops/generator`: the generator already depends on this package, so
|
|
8
|
-
* importing back would be a cycle. The vocabulary mirrors that block field for
|
|
9
|
-
* field — the same arrangement, and for the same reason, as `IndexingConfig`
|
|
10
|
-
* and `NotifyContext` beside it.
|
|
11
|
-
*
|
|
12
|
-
* Optional fields are written `?: T | undefined` throughout: `exactOptionalPropertyTypes`
|
|
13
|
-
* is on, and these describe a *parsed JSON config* where an absent key and an
|
|
14
|
-
* explicit `undefined` are the same thing.
|
|
15
|
-
*/
|
|
16
|
-
type CrmProvider = 'hubspot' | 'bigin' | 'jobber';
|
|
1
|
+
import { t as ConversionEvent } from "../types-oac2P5or.mjs";
|
|
2
|
+
import { BiginConfig, BiginPipelineConfig, CrmConfig, HubSpotConfig, JobberConfig } from "@getvitops/config";
|
|
3
|
+
import { BIGIN_DATA_CENTERS, CrmProviderConfig } from "@getvitops/config/providers";
|
|
4
|
+
type CrmProvider = CrmProviderConfig;
|
|
17
5
|
/** Human-readable label per provider, for the notification email's Delivery
|
|
18
6
|
* line (`describeCrmResult`) and anywhere else a provider must be named
|
|
19
7
|
* without a hand-written ternary. Exhaustive by type — a provider missing
|
|
20
8
|
* here is a compile error, not a mislabelled email. */
|
|
21
9
|
export declare const PROVIDER_LABELS: Record<CrmProvider, string>;
|
|
22
10
|
/** Bigin's data centre — the region a self-client's tokens and API host belong to. */
|
|
23
|
-
type BiginDataCenter =
|
|
24
|
-
interface HubSpotConfig {
|
|
25
|
-
portalId: string;
|
|
26
|
-
formGuid: string;
|
|
27
|
-
/** Default `na`. Only affects the submissions API host. */
|
|
28
|
-
dataCenter?: 'na' | 'eu' | undefined;
|
|
29
|
-
}
|
|
30
|
-
interface BiginPipelineConfig {
|
|
31
|
-
enabled: boolean;
|
|
32
|
-
subPipeline?: string | undefined;
|
|
33
|
-
stage?: string | undefined;
|
|
34
|
-
}
|
|
35
|
-
interface BiginConfig {
|
|
36
|
-
dataCenter: BiginDataCenter;
|
|
37
|
-
/** Default `Email` — Bigin's own unique field, used for `upsert`'s dedupe. */
|
|
38
|
-
duplicateCheckField?: string | undefined;
|
|
39
|
-
pipeline?: BiginPipelineConfig | undefined;
|
|
40
|
-
}
|
|
41
|
-
interface JobberConfig {
|
|
42
|
-
/**
|
|
43
|
-
* The pinned `X-JOBBER-GRAPHQL-VERSION` date this integration was built and
|
|
44
|
-
* tested against. Overriding it is an escape hatch for a client's app
|
|
45
|
-
* pinned to a different version — not something to reach for casually,
|
|
46
|
-
* since Jobber's schema can change field names between versions.
|
|
47
|
-
*/
|
|
48
|
-
apiVersion?: string | undefined;
|
|
49
|
-
/**
|
|
50
|
-
* Look up an existing client by email before creating one (default
|
|
51
|
-
* **false**). Jobber's `clientCreate` has no upsert, so a resubmitting
|
|
52
|
-
* visitor creates a second client unless this runs — but the lookup query's
|
|
53
|
-
* filter shape is not yet verified against a live Jobber schema (see
|
|
54
|
-
* `jobber.ts`'s docblock), so it defaults off rather than risk a wrong
|
|
55
|
-
* query silently blocking a real submission. Set `true` once verified for
|
|
56
|
-
* your account.
|
|
57
|
-
*/
|
|
58
|
-
dedupe?: boolean | undefined;
|
|
59
|
-
note?: {
|
|
60
|
-
/** Attach a best-effort note carrying the message + unmapped
|
|
61
|
-
* attribution (default true). A failure here never downgrades the
|
|
62
|
-
* client write's own success — see `jobber.ts`. */
|
|
63
|
-
enabled?: boolean | undefined;
|
|
64
|
-
} | undefined;
|
|
65
|
-
}
|
|
66
|
-
interface CrmConfig {
|
|
67
|
-
provider: CrmProvider;
|
|
68
|
-
enabled?: boolean | undefined;
|
|
69
|
-
hubspot?: HubSpotConfig | undefined;
|
|
70
|
-
bigin?: BiginConfig | undefined;
|
|
71
|
-
jobber?: JobberConfig | undefined;
|
|
72
|
-
/** Submitted field name → CRM property name. Unmapped fields are dropped, not guessed. */
|
|
73
|
-
fieldMap?: Record<string, string> | undefined;
|
|
74
|
-
/** `_ac` tracking key → CRM property name. Same drop-if-unmapped rule. */
|
|
75
|
-
trackingFields?: Record<string, string> | undefined;
|
|
76
|
-
}
|
|
11
|
+
type BiginDataCenter = (typeof BIGIN_DATA_CENTERS)[number];
|
|
77
12
|
/** Credentials, never config — always injected by the caller from env vars. */
|
|
78
13
|
interface HubSpotCredentials {
|
|
79
14
|
accessToken: string;
|
|
@@ -142,8 +77,6 @@ interface CrmResult {
|
|
|
142
77
|
/** The provider's own error/status code, when it gave one. */
|
|
143
78
|
code?: string | undefined;
|
|
144
79
|
}
|
|
145
|
-
//#endregion
|
|
146
|
-
//#region src/crm/hubspot.d.ts
|
|
147
80
|
interface HubSpotSubmitContext {
|
|
148
81
|
hutk?: string | undefined;
|
|
149
82
|
pageUri?: string | undefined;
|
|
@@ -161,8 +94,6 @@ interface HubSpotSubmitContext {
|
|
|
161
94
|
* form); 429 is the per-portal rate limit.
|
|
162
95
|
*/
|
|
163
96
|
export declare function submitToHubSpot(record: CrmRecord, config: HubSpotConfig, credentials: HubSpotCredentials, context?: HubSpotSubmitContext, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<CrmResult>;
|
|
164
|
-
//#endregion
|
|
165
|
-
//#region src/crm/map.d.ts
|
|
166
97
|
/**
|
|
167
98
|
* Bigin requires `Last_Name`; a form that only asked for a full name or an
|
|
168
99
|
* email needs one derived rather than 400ing on insert.
|
|
@@ -184,27 +115,19 @@ export declare function deriveLastName(identity: {
|
|
|
184
115
|
refusal: string;
|
|
185
116
|
};
|
|
186
117
|
export declare function mapSubmission(event: ConversionEvent, config: CrmConfig): CrmRecord;
|
|
187
|
-
//#endregion
|
|
188
|
-
//#region src/crm/plan.d.ts
|
|
189
118
|
export declare function planCrm(config: CrmConfig | undefined, credentials: CrmCredentials | undefined): CrmPlan;
|
|
190
|
-
//#endregion
|
|
191
|
-
//#region src/crm/bigin.d.ts
|
|
192
119
|
export declare function zohoApiHost(dataCenter: string): string;
|
|
193
120
|
/**
|
|
194
121
|
* Write one record to Bigin. Never throws — a token-exchange failure, a
|
|
195
122
|
* network error and a rejected response all come back as `CrmResult`.
|
|
196
123
|
*/
|
|
197
124
|
export declare function submitToBigin(record: CrmRecord, config: BiginConfig, credentials: BiginCredentials, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<CrmResult>;
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region src/crm/jobber.d.ts
|
|
200
125
|
/**
|
|
201
126
|
* Write one record to Jobber. Never throws — a token-exchange failure, a
|
|
202
127
|
* network error, a top-level GraphQL `errors[]`, and a `userErrors[]` all
|
|
203
128
|
* come back as a `CrmResult` with `ok: false`.
|
|
204
129
|
*/
|
|
205
130
|
export declare function submitToJobber(record: CrmRecord, config: JobberConfig, credentials: JobberCredentials, pageUri: string | undefined, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<CrmResult>;
|
|
206
|
-
//#endregion
|
|
207
|
-
//#region src/crm/zoho-token.d.ts
|
|
208
131
|
export declare function zohoAccountsHost(dataCenter: string): string;
|
|
209
132
|
interface ZohoOAuth {
|
|
210
133
|
clientId: string;
|
|
@@ -221,8 +144,6 @@ interface ZohoOAuth {
|
|
|
221
144
|
export declare function zohoAccessToken(oauth: ZohoOAuth, dataCenter: string, fetchImpl?: typeof fetch, now?: number): Promise<string>;
|
|
222
145
|
/** Test-only: clear the module-level cache between cases. */
|
|
223
146
|
export declare function clearZohoTokenCache(): void;
|
|
224
|
-
//#endregion
|
|
225
|
-
//#region src/crm/jobber-token.d.ts
|
|
226
147
|
/**
|
|
227
148
|
* Jobber OAuth2 refresh-token exchange.
|
|
228
149
|
*
|
|
@@ -264,8 +185,6 @@ interface JobberOAuth {
|
|
|
264
185
|
export declare function jobberAccessToken(oauth: JobberOAuth, fetchImpl?: typeof fetch, now?: number): Promise<string>;
|
|
265
186
|
/** Test-only: clear the module-level cache between cases. */
|
|
266
187
|
export declare function clearJobberTokenCache(): void;
|
|
267
|
-
//#endregion
|
|
268
|
-
//#region src/crm/index.d.ts
|
|
269
188
|
export interface CrmDeps {
|
|
270
189
|
credentials?: CrmCredentials | undefined;
|
|
271
190
|
fetch?: typeof fetch | undefined;
|
|
@@ -283,6 +202,5 @@ export declare function deliverToCrm(event: ConversionEvent, config: CrmConfig |
|
|
|
283
202
|
/** A one-line, human-readable delivery note — what `conversion.ts` folds into
|
|
284
203
|
* the notification email's "Delivery" section. */
|
|
285
204
|
export declare function describeCrmResult(result: CrmResult | null): string | null;
|
|
286
|
-
//#endregion
|
|
287
205
|
export type { BiginConfig, BiginCredentials, BiginDataCenter, BiginPipelineConfig, CrmConfig, CrmCredentials, CrmPlan, CrmProvider, CrmRecord, CrmResult, HubSpotConfig, HubSpotCredentials, HubSpotSubmitContext, JobberConfig, JobberCredentials, JobberOAuth };
|
|
288
206
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/crm/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/crm/types.ts","../../src/crm/hubspot.ts","../../src/crm/map.ts","../../src/crm/plan.ts","../../src/crm/bigin.ts","../../src/crm/jobber.ts","../../src/crm/zoho-token.ts","../../src/crm/jobber-token.ts","../../src/crm/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/crm/types.ts","../../src/crm/hubspot.ts","../../src/crm/map.ts","../../src/crm/plan.ts","../../src/crm/bigin.ts","../../src/crm/jobber.ts","../../src/crm/zoho-token.ts","../../src/crm/jobber-token.ts","../../src/crm/index.ts"],"mappings":";;;KAqBY,cAAc;;;;;qBAMb,iBAAiB,OAAO;;KAOzB,0BAA0B;;UAGrB;EACf;;UAGe;EACf;EACA;EACA;;;;;;;;;UAUe;EACf;EACA;EACA;;KAGU;EACN;EAAqB,SAAS;;EAC9B;EAAmB,OAAO;;EAC1B;EAAoB,QAAQ;;;UAGjB;EACf;EACA,WAAW;;EAEX;;;;UAKe;EACf;EACA;EACA;EACA;EACA;EACA;;EAEA,OAAO;;;EAGP,aAAa;;;;;;;UAQE;EACf;EACA,UAAU;;EAEV;;EAEA;;EAEA;;UC5De;EACf;EACA;EACA;;;;;;;;;;;;;wBAcoB,gBACpB,QAAQ,WACR,QAAQ,eACR,aAAa,oBACb,UAAS,sBACT,mBAAkB,OAClB,qBACC,QAAQ;;;;;;;;;;;wBCtCK,eAAe;EAC7B;EACA;EACA;EACA;;EACI;;EAAuB;;wBAgDb,cAAc,OAAO,iBAAiB,QAAQ,YAAY;wBC/C1D,QACd,QAAQ,uBACR,aAAa,6BACZ;wBCba,YAAY;;;;;wBAsBN,cACpB,QAAQ,WACR,QAAQ,aACR,aAAa,kBACb,mBAAkB,OAClB,qBACC,QAAQ;;;;;;wBCoJW,eACpB,QAAQ,WACR,QAAQ,cACR,aAAa,mBACb,6BACA,mBAAkB,OAClB,qBACC,QAAQ;wBCpLK,iBAAiB;UAehB;EACf;EACA;EACA;;;;;;;;;wBAUoB,gBACpB,OAAO,WACP,oBACA,mBAAkB,OAClB,eACC;;wBAkCa;;;;;;;;;;;;;;;;;;;;;UC3DC;EACf;EACA;EACA;;;;;;;;;;;;;;;wBAgBoB,kBACpB,OAAO,aACP,mBAAkB,OAClB,eACC;;wBA8Ca;iBCvEC;EACf,cAAc;EACd,eAAe;EACf;;;;;;;;EAQA,cAAc;;wBAGM,aACpB,OAAO,iBACP,QAAQ,uBACR,OAAM,UACL,QAAQ;;;wBAwDK,kBAAkB,QAAQ"}
|
package/dist/crm/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as identifySubmitter } from "../identity-
|
|
1
|
+
import { t as identifySubmitter } from "../identity-C4yInICW.mjs";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// src/crm/map.ts
|
|
4
4
|
/**
|
|
5
5
|
* Turning one submission into a provider-neutral `CrmRecord`.
|
|
6
6
|
*
|
|
@@ -68,8 +68,7 @@ function mapSubmission(event, config) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
//#region src/crm/zoho-token.ts
|
|
71
|
+
// src/crm/zoho-token.ts
|
|
73
72
|
const ZOHO_ACCOUNTS_HOSTS = {
|
|
74
73
|
com: "accounts.zoho.com",
|
|
75
74
|
ca: "accounts.zohocloud.ca",
|
|
@@ -121,8 +120,7 @@ function clearZohoTokenCache() {
|
|
|
121
120
|
cache$1.clear();
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
//#region src/crm/bigin.ts
|
|
123
|
+
// src/crm/bigin.ts
|
|
126
124
|
/**
|
|
127
125
|
* Zoho Bigin executor — decides nothing except the one thing it must refuse
|
|
128
126
|
* to send: a record with no derivable last name (see `deriveLastName` in
|
|
@@ -263,8 +261,7 @@ async function createPipelineRecord(record, config, contactId, host, token, fetc
|
|
|
263
261
|
});
|
|
264
262
|
}
|
|
265
263
|
|
|
266
|
-
|
|
267
|
-
//#region src/crm/hubspot.ts
|
|
264
|
+
// src/crm/hubspot.ts
|
|
268
265
|
const HUBSPOT_HOSTS = {
|
|
269
266
|
na: "api.hsforms.com",
|
|
270
267
|
eu: "api-eu1.hsforms.com"
|
|
@@ -358,8 +355,7 @@ async function submitToHubSpot(record, config, credentials, context = {}, fetchI
|
|
|
358
355
|
};
|
|
359
356
|
}
|
|
360
357
|
|
|
361
|
-
|
|
362
|
-
//#region src/crm/jobber-token.ts
|
|
358
|
+
// src/crm/jobber-token.ts
|
|
363
359
|
/**
|
|
364
360
|
* Jobber OAuth2 refresh-token exchange.
|
|
365
361
|
*
|
|
@@ -429,8 +425,7 @@ function clearJobberTokenCache() {
|
|
|
429
425
|
cache.clear();
|
|
430
426
|
}
|
|
431
427
|
|
|
432
|
-
|
|
433
|
-
//#region src/crm/jobber.ts
|
|
428
|
+
// src/crm/jobber.ts
|
|
434
429
|
const JOBBER_GRAPHQL_ENDPOINT = "https://api.getjobber.com/api/graphql";
|
|
435
430
|
/** Pinned to the version this integration was built and tested against.
|
|
436
431
|
* Jobber's `X-JOBBER-GRAPHQL-VERSION` header is mandatory — an unversioned
|
|
@@ -619,8 +614,7 @@ async function submitToJobber(record, config, credentials, pageUri, fetchImpl =
|
|
|
619
614
|
};
|
|
620
615
|
}
|
|
621
616
|
|
|
622
|
-
|
|
623
|
-
//#region src/crm/plan.ts
|
|
617
|
+
// src/crm/plan.ts
|
|
624
618
|
/**
|
|
625
619
|
* What each provider requires to be considered configured, as one table
|
|
626
620
|
* rather than a per-provider `if`. A provider missing here is a type error —
|
|
@@ -674,8 +668,7 @@ function planCrm(config, credentials) {
|
|
|
674
668
|
};
|
|
675
669
|
}
|
|
676
670
|
|
|
677
|
-
|
|
678
|
-
//#region src/crm/types.ts
|
|
671
|
+
// src/crm/types.ts
|
|
679
672
|
/** Human-readable label per provider, for the notification email's Delivery
|
|
680
673
|
* line (`describeCrmResult`) and anywhere else a provider must be named
|
|
681
674
|
* without a hand-written ternary. Exhaustive by type — a provider missing
|
|
@@ -686,8 +679,7 @@ const PROVIDER_LABELS = {
|
|
|
686
679
|
jobber: "Jobber"
|
|
687
680
|
};
|
|
688
681
|
|
|
689
|
-
|
|
690
|
-
//#region src/crm/index.ts
|
|
682
|
+
// src/crm/index.ts
|
|
691
683
|
async function deliverToCrm(event, config, deps = {}) {
|
|
692
684
|
const plan = planCrm(config, deps.credentials);
|
|
693
685
|
if (!plan.enabled || !config || !deps.credentials) return config ? {
|
|
@@ -726,6 +718,5 @@ function describeCrmResult(result) {
|
|
|
726
718
|
return `⚠ Not written to ${name}: ${result.reason ?? "unknown reason"}`;
|
|
727
719
|
}
|
|
728
720
|
|
|
729
|
-
//#endregion
|
|
730
721
|
export { PROVIDER_LABELS, clearJobberTokenCache, clearZohoTokenCache, deliverToCrm, deriveLastName, describeCrmResult, jobberAccessToken, mapSubmission, planCrm, submitToBigin, submitToHubSpot, submitToJobber, zohoAccessToken, zohoAccountsHost, zohoApiHost };
|
|
731
722
|
//# sourceMappingURL=index.mjs.map
|