From 507d74d7542d45c7452a74ba89d9a9c7831f029d Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 27 Oct 2026 18:44:59 +0100 Subject: [PATCH 01/12] Move color palette functions to @elastic/eui-theme-common Makes euiPaletteColorBlind and the other palette functions individually importable without loading the whole @elastic/eui package. Kibana server code needs the palettes (pure, chroma-js based) but a single import of the @elastic/eui barrel loads 1,100+ modules of React components into the Node.js server process. - colorPalette and the euiPalette* functions move to @elastic/eui-theme-common, next to the vis color store they read from; the original modules in @elastic/eui re-export them unchanged, so the public API of @elastic/eui is unaffected (including types). - Store initialization moves with them: the vis color store previously got its default colors from @elastic/eui importing @elastic/eui-theme-borealis; now @elastic/eui-theme-borealis initializes the store with its default colors on import (getInstance is idempotent), so palettes work when only the theme packages are loaded. EuiVisColorStore.getInstance() without arguments now throws a descriptive error if the store was never initialized. - React hooks (useEuiPalette*) stay in @elastic/eui. Co-Authored-By: Claude Fable 5 --- packages/eui-theme-borealis/src/index.ts | 12 +- .../src/services/color_palette.test.ts | 39 ++ .../src/services/color_palette.ts | 95 ++++ .../src/services/eui_palettes.ts | 469 +++++++++++++++++ .../eui-theme-common/src/services/index.ts | 2 + .../src/services/vis_color_store.ts | 9 +- .../eui/src/services/color/color_palette.ts | 91 +--- .../eui/src/services/color/eui_palettes.ts | 476 +----------------- 8 files changed, 650 insertions(+), 543 deletions(-) create mode 100644 packages/eui-theme-common/src/services/color_palette.test.ts create mode 100644 packages/eui-theme-common/src/services/color_palette.ts create mode 100644 packages/eui-theme-common/src/services/eui_palettes.ts diff --git a/packages/eui-theme-borealis/src/index.ts b/packages/eui-theme-borealis/src/index.ts index 0cbeea36a0ae..179584d0d9d0 100644 --- a/packages/eui-theme-borealis/src/index.ts +++ b/packages/eui-theme-borealis/src/index.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ -import { buildTheme, EuiThemeShape } from '@elastic/eui-theme-common'; +import { + buildTheme, + EuiThemeShape, + EuiVisColorStore, +} from '@elastic/eui-theme-common'; import { colors } from './variables/colors'; import { animation } from './variables/_animation'; @@ -25,6 +29,12 @@ import { colorVisDark } from './variables/colors/_colors_vis_dark'; export { colorVisLight as colorVis, colorVisLight, colorVisDark }; +// Initialize the global vis color store with this theme's default colors so the +// palette functions exported by @elastic/eui-theme-common work without loading +// @elastic/eui. getInstance is idempotent: if the store already exists this is +// a no-op. +EuiVisColorStore.getInstance(colorVisLight); + export const EUI_THEME_BOREALIS_KEY = 'EUI_THEME_BOREALIS'; export const euiThemeBorealis: EuiThemeShape = { diff --git a/packages/eui-theme-common/src/services/color_palette.test.ts b/packages/eui-theme-common/src/services/color_palette.test.ts new file mode 100644 index 000000000000..ef1225dac88c --- /dev/null +++ b/packages/eui-theme-common/src/services/color_palette.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { colorPalette } from './color_palette'; + +describe('colorPalette', () => { + it('should generate the expected palette', () => { + const actualPalette = colorPalette(['#FFFF6D', '#1EA593']); + expect(actualPalette).toEqual([ + '#ffff6d', + '#ecf573', + '#d8eb79', + '#c4e07d', + '#b0d682', + '#9acc86', + '#84c28a', + '#6bb98d', + '#4daf90', + '#1ea593', + ]); + }); + + it('should generate a palette with the specified spread', () => { + const actualPalette = colorPalette(['#FFFF6D', '#1EA593'], 6); + expect(actualPalette).toEqual([ + '#ffff6d', + '#dced77', + '#b8da80', + '#92c887', + '#66b78e', + '#1ea593', + ]); + }); +}); diff --git a/packages/eui-theme-common/src/services/color_palette.ts b/packages/eui-theme-common/src/services/color_palette.ts new file mode 100644 index 000000000000..1f8927d4d92a --- /dev/null +++ b/packages/eui-theme-common/src/services/color_palette.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import chroma from 'chroma-js'; + +export const MID_COLOR_STOP = '#EBEFF5'; + +/** + * This function takes an array of colors and returns an array of interpolated + * colors based on the number of steps/len needed for use in UI elements such as charts. + * Derived from https://github.com/gka/palettes (Unlicensed) + */ + +export function colorPalette( + /** + * The main color code or array of codes + */ + colors: string[], + /** + * The number of colors in the resulting array (default 10) + */ + len: number = 10, + /** + * Forces color interpolation to be calculated separately for each half (default false) + */ + diverging: boolean = false, + /** + * Uses a more static interpolation for non-continuous spectrums + */ + categorical: boolean = false +) { + let hexStart: string[] = colors.slice(); + let hexEnd: string[] = []; + + const even = len % 2 === 0; + const numColorsLeft = diverging ? Math.ceil(len / 2) + (even ? 1 : 0) : len; + const numColorsRight = diverging ? Math.ceil(len / 2) + (even ? 1 : 0) : 0; + + // If only a single color is provided prepend the mid-point color + if (hexStart.length === 1) { + hexStart.unshift(MID_COLOR_STOP); + } + + // If diverging, split the start array and duplicate the mid color + if (diverging) { + // If there's no midpoint, create one + if (hexStart.length < 3) { + hexStart[2] = hexStart[1]; + hexStart[1] = MID_COLOR_STOP; + } + + const numColorsHalf = Math.ceil(hexStart.length / 2); + + const colorsLeft = hexStart.filter(function (item, index) { + if (index < numColorsHalf) { + return true; // keep it + } + }); + const colorsRight = hexStart + .reverse() + .filter(function (item, index) { + if (index < numColorsHalf) { + return true; // keep it + } + }) + .reverse(); + + hexStart = colorsLeft; + hexEnd = colorsRight; + } + + function createSteps(colors: string[], steps: number) { + if (!colors.length) { + return colors; + } + + if (!categorical) { + return chroma.bezier(colors).scale().correctLightness().colors(steps); + } else { + return chroma.scale(colors).colors(steps); + } + } + + const stepsLeft = createSteps(hexStart, numColorsLeft); + const stepsRight = createSteps(hexEnd, numColorsRight); + + return ( + even && diverging ? stepsLeft.slice(0, stepsLeft.length - 1) : stepsLeft + ).concat(stepsRight.slice(1)); +} diff --git a/packages/eui-theme-common/src/services/eui_palettes.ts b/packages/eui-theme-common/src/services/eui_palettes.ts new file mode 100644 index 000000000000..bef72359c2b4 --- /dev/null +++ b/packages/eui-theme-common/src/services/eui_palettes.ts @@ -0,0 +1,469 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import chroma from 'chroma-js'; +import { colorPalette } from './color_palette'; +import { EuiVisColorStore } from './vis_color_store'; +import { _EuiThemeVisColors } from '../global_styling'; + +export type EuiPalette = string[]; + +/** + * Resolves the default vis colors from the global vis color store singleton. + * The store is initialized by the theme packages (e.g. `@elastic/eui-theme-borealis`) + * and kept up to date by `EuiProvider` on theme change. + */ +const getDefaultVisColors = () => EuiVisColorStore.getInstance().visColors; + +const flatten = (arr: any[]) => [].concat(...arr); + +const euiPalette = function ( + colors: string[], + steps: number, + diverge: boolean = false, + categorical: boolean = true +): EuiPalette { + // This function also trims the first color so white/black is never a color + if (!diverge && steps > 1) { + const palette = colorPalette(colors, steps + 1); + palette.shift(); + return palette; + } + + return colorPalette(colors, steps, diverge, categorical); +}; + +export type EuiPaletteCommonProps = { + /** + * Defines the default set of colors the palette uses. + * Defaults to vis colors from `EUI_VIS_COLOR_STORE`. + * Use this to specify colors when you can't rely on the EuiProvider updates. + */ + colors?: _EuiThemeVisColors; +}; + +export type EuiPaletteRotationProps = { + /** + * How many variations of the series is needed + */ + rotations?: number; + /** + * Order similar colors as `group`s or just `append` each variation + */ + order?: 'append' | 'group'; + /** + * Specifies if the direction of the color variations + */ + direction?: 'lighter' | 'darker' | 'both'; + /** + * Use the default sort order, or re-sort them based on the color wheel (natural) + */ + sortBy?: 'default' | 'natural'; + /** + * Shift the sorting order by a certain number when used in conjunction with `'natural'` `sortBy`. + * Defaults to a number close to green. + */ + sortShift?: string; +}; + +export type EuiPaletteColorBlindProps = EuiPaletteCommonProps & + EuiPaletteRotationProps; + +/** + * For usage in React use the `useEuiPaletteColorBlind` hook instead. + * + * NOTE: These functions rely on base vis colors of the theme which are provided via a global + * singleton instance `EUI_VIS_COLOR_STORE` that's updated by the EuiProvider on theme change. + * Make sure the function is recalled on theme change to retrieve theme-related colors. + * + * Outside of a react component you can use the `subscibe()` method on the `EUI_VIS_COLOR_STORE` + * to subscribe to updates and update your usages to ensure latest colors are loaded. + */ +export const euiPaletteColorBlind = ({ + rotations = 1, + order = 'append', + direction = 'lighter', + sortBy = 'default', + sortShift = '-100', + colors, +}: EuiPaletteColorBlindProps = {}): EuiPalette => { + let _colors: string[] = []; + + const visColors = colors ?? getDefaultVisColors(); + + let base = [ + visColors.euiColorVis0, + visColors.euiColorVis1, + visColors.euiColorVis2, + visColors.euiColorVis3, + visColors.euiColorVis4, + visColors.euiColorVis5, + visColors.euiColorVis6, + visColors.euiColorVis7, + visColors.euiColorVis8, + visColors.euiColorVis9, + ]; + + if (sortBy === 'natural') { + // Sort the colors based on the color wheel, but shifting the values based on sortShift + base = [...base].sort(function (a, b) { + return ( + chroma(a).set('hsl.h', sortShift).hsl()[0] - + chroma(b).set('hsl.h', sortShift).hsl()[0] + ); + }); + } + + if (rotations > 1) { + const palettes = base.map((color) => { + // Create the darkest and lightest versions of each color using black and white + const palette = colorPalette(['black', color, 'white'], 5, false, true); + // Then removing the extremes + palette.pop(); + palette.shift(); + + switch (direction) { + case 'lighter': + return colorPalette([palette[1], palette[2]], rotations, false, true); + case 'darker': + return colorPalette([palette[1], palette[0]], rotations, false, true); + case 'both': + return colorPalette(palette, rotations, false, true); + } + }); + + if (order === 'group') { + _colors = flatten(palettes); + } else { + for (let i = 0; i < rotations; i++) { + const rotation = palettes.map((palette) => palette[i]); + _colors.push(...rotation); + } + } + } else { + _colors = base; + } + + return _colors; +}; + +/** + * For usage in React use the `useEuiPaletteColorBlindBehindText` hook instead. + * + * Color blind palette with text is meant for use when text is applied on top of the color. + * It increases the brightness of the color to give the text more contrast. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteColorBlindBehindText = ( + paletteProps: EuiPaletteColorBlindProps = {} +) => { + const originalPalette = euiPaletteColorBlind(paletteProps); + + return originalPalette; +}; + +/** + * For usage in React use the `useEuiPaletteForStatus` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteForStatus = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisSuccess0]; + } + if (steps <= 3) { + return euiPalette( + [ + visColors.euiColorVisSuccess0, + visColors.euiColorVisWarning1, + visColors.euiColorVisDanger0, + ], + steps, + true + ); + } + return euiPalette( + [ + visColors.euiColorVisSuccess0, + visColors.euiColorVisSuccess1, + visColors.euiColorVisWarning1, + visColors.euiColorVisDanger1, + visColors.euiColorVisDanger0, + ], + steps, + true + ); +}; + +/** + * For usage in React use the `useEuiPaletteForTemperature` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteForTemperature = function ( + steps: any, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisCool2]; + } else if (steps <= 3) { + return euiPalette( + [visColors.euiColorVisCool2, visColors.euiColorVisWarm2], + steps, + true + ); + } + + const cools = colorPalette( + [ + visColors.euiColorVisCool2, + visColors.euiColorVisCool1, + visColors.euiColorVisCool0, + ], + 3 + ); + const warms = colorPalette( + [ + visColors.euiColorVisWarm0, + visColors.euiColorVisWarm1, + visColors.euiColorVisWarm2, + ], + 3 + ); + + const paletteColors = [...cools, visColors.euiColorVisBase0, ...warms]; + + return euiPalette(paletteColors, steps, true); +}; + +/** + * For usage in React use the `useEuiPaletteComplementary` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteComplementary = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisComplementary0]; + } + + const paletteColors = [ + visColors.euiColorVisComplementary0, + visColors.euiColorVisBase0, + visColors.euiColorVisComplementary1, + ]; + + return euiPalette(paletteColors, steps, true); +}; + +/** + * For usage in React use the `useEuiPaletteRed` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteRed = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisDanger1]; + } + + return euiPalette( + [visColors.euiColorVisBase0, visColors.euiColorVisDanger0], + steps + ); +}; + +/** + * For usage in React use the `useEuiPaletteGreen` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteGreen = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisSuccess1]; + } + + return euiPalette( + [visColors.euiColorVisBase0, visColors.euiColorVisSuccess0], + steps + ); +}; + +/** + * For usage in React use the `useEuiPaletteSkyBlue` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteSkyBlue = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisNeutral1]; + } + + return euiPalette( + [visColors.euiColorVisBase0, visColors.euiColorVisNeutral0], + steps + ); +}; + +/** + * For usage in React use the `useEuiPaletteYellow` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteYellow = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisWarning1]; + } + + return euiPalette( + [visColors.euiColorVisBase0, visColors.euiColorVisWarning0], + steps + ); +}; + +/** + * For usage in React use the `useEuiPaletteOrange` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteOrange = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisRisk1]; + } + + return euiPalette( + [visColors.euiColorVisBase0, visColors.euiColorVisRisk0], + steps + ); +}; + +/** + * For usage in React use the `useEuiPaletteCool` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteCool = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisCool1]; + } + + return euiPalette( + [ + visColors.euiColorVisCool0, + visColors.euiColorVisCool1, + visColors.euiColorVisCool2, + ], + steps + ); +}; + +/** + * For usage in React use the `useEuiPaletteWarm` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteWarm = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisWarm2]; + } + + return euiPalette( + [ + visColors.euiColorVisWarm0, + visColors.euiColorVisWarm1, + visColors.euiColorVisWarm2, + ], + steps + ); +}; + +/** + * For usage in React use the `useEuiPaletteGray` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteGray = function ( + steps: number, + { colors }: EuiPaletteCommonProps = {} +): EuiPalette { + const visColors = colors ?? getDefaultVisColors(); + + if (steps === 1) { + return [visColors.euiColorVisGrey1]; + } + + return euiPalette( + [ + visColors.euiColorVisBase0, + visColors.euiColorVisGrey0, + visColors.euiColorVisGrey1, + visColors.euiColorVisGrey2, + visColors.euiColorVisGrey3, + ], + steps, + false + ); +}; diff --git a/packages/eui-theme-common/src/services/index.ts b/packages/eui-theme-common/src/services/index.ts index 464a0a1b58b3..fbe546b1c93b 100644 --- a/packages/eui-theme-common/src/services/index.ts +++ b/packages/eui-theme-common/src/services/index.ts @@ -6,4 +6,6 @@ * Side Public License, v 1. */ +export * from './color_palette'; +export * from './eui_palettes'; export * from './vis_color_store'; diff --git a/packages/eui-theme-common/src/services/vis_color_store.ts b/packages/eui-theme-common/src/services/vis_color_store.ts index 4c46b25b7357..97e43ed21a5c 100644 --- a/packages/eui-theme-common/src/services/vis_color_store.ts +++ b/packages/eui-theme-common/src/services/vis_color_store.ts @@ -81,8 +81,15 @@ class EuiVisColorStoreImpl implements _EuiVisColorStore { export class EuiVisColorStore { private static instance: EuiVisColorStoreImpl; - static getInstance(defaultColors: _EuiThemeVisColors): EuiVisColorStoreImpl { + static getInstance(defaultColors?: _EuiThemeVisColors): EuiVisColorStoreImpl { if (!this.instance) { + if (!defaultColors) { + throw new Error( + 'EuiVisColorStore was accessed before it was initialized with default colors. ' + + 'Import a theme package (e.g. `@elastic/eui-theme-borealis`) or `@elastic/eui` first.' + ); + } + this.instance = new EuiVisColorStoreImpl({ defaultColors, }); diff --git a/packages/eui/src/services/color/color_palette.ts b/packages/eui/src/services/color/color_palette.ts index 1f8927d4d92a..008bfb9ce3c6 100644 --- a/packages/eui/src/services/color/color_palette.ts +++ b/packages/eui/src/services/color/color_palette.ts @@ -6,90 +6,7 @@ * Side Public License, v 1. */ -import chroma from 'chroma-js'; - -export const MID_COLOR_STOP = '#EBEFF5'; - -/** - * This function takes an array of colors and returns an array of interpolated - * colors based on the number of steps/len needed for use in UI elements such as charts. - * Derived from https://github.com/gka/palettes (Unlicensed) - */ - -export function colorPalette( - /** - * The main color code or array of codes - */ - colors: string[], - /** - * The number of colors in the resulting array (default 10) - */ - len: number = 10, - /** - * Forces color interpolation to be calculated separately for each half (default false) - */ - diverging: boolean = false, - /** - * Uses a more static interpolation for non-continuous spectrums - */ - categorical: boolean = false -) { - let hexStart: string[] = colors.slice(); - let hexEnd: string[] = []; - - const even = len % 2 === 0; - const numColorsLeft = diverging ? Math.ceil(len / 2) + (even ? 1 : 0) : len; - const numColorsRight = diverging ? Math.ceil(len / 2) + (even ? 1 : 0) : 0; - - // If only a single color is provided prepend the mid-point color - if (hexStart.length === 1) { - hexStart.unshift(MID_COLOR_STOP); - } - - // If diverging, split the start array and duplicate the mid color - if (diverging) { - // If there's no midpoint, create one - if (hexStart.length < 3) { - hexStart[2] = hexStart[1]; - hexStart[1] = MID_COLOR_STOP; - } - - const numColorsHalf = Math.ceil(hexStart.length / 2); - - const colorsLeft = hexStart.filter(function (item, index) { - if (index < numColorsHalf) { - return true; // keep it - } - }); - const colorsRight = hexStart - .reverse() - .filter(function (item, index) { - if (index < numColorsHalf) { - return true; // keep it - } - }) - .reverse(); - - hexStart = colorsLeft; - hexEnd = colorsRight; - } - - function createSteps(colors: string[], steps: number) { - if (!colors.length) { - return colors; - } - - if (!categorical) { - return chroma.bezier(colors).scale().correctLightness().colors(steps); - } else { - return chroma.scale(colors).colors(steps); - } - } - - const stepsLeft = createSteps(hexStart, numColorsLeft); - const stepsRight = createSteps(hexEnd, numColorsRight); - - return ( - even && diverging ? stepsLeft.slice(0, stepsLeft.length - 1) : stepsLeft - ).concat(stepsRight.slice(1)); -} +// The implementation lives in @elastic/eui-theme-common so that color palettes +// are usable without importing @elastic/eui; re-exported here for backwards +// compatibility. +export { MID_COLOR_STOP, colorPalette } from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/services/color/eui_palettes.ts b/packages/eui/src/services/color/eui_palettes.ts index ebed077b4f01..0f82ff52ca5c 100644 --- a/packages/eui/src/services/color/eui_palettes.ts +++ b/packages/eui/src/services/color/eui_palettes.ts @@ -6,457 +6,25 @@ * Side Public License, v 1. */ -import chroma from 'chroma-js'; -import { colorPalette } from './color_palette'; -import { EUI_VIS_COLOR_STORE } from './vis_color_store'; -import { _EuiThemeVisColors } from '@elastic/eui-theme-common'; - -export type EuiPalette = string[]; - -const flatten = (arr: any[]) => [].concat(...arr); - -const euiPalette = function ( - colors: string[], - steps: number, - diverge: boolean = false, - categorical: boolean = true -): EuiPalette { - // This function also trims the first color so white/black is never a color - if (!diverge && steps > 1) { - const palette = colorPalette(colors, steps + 1); - palette.shift(); - return palette; - } - - return colorPalette(colors, steps, diverge, categorical); -}; - -export type EuiPaletteCommonProps = { - /** - * Defines the default set of colors the palette uses. - * Defaults to vis colors from `EUI_VIS_COLOR_STORE`. - * Use this to specify colors when you can't rely on the EuiProvider updates. - */ - colors?: _EuiThemeVisColors; -}; - -export type EuiPaletteRotationProps = { - /** - * How many variations of the series is needed - */ - rotations?: number; - /** - * Order similar colors as `group`s or just `append` each variation - */ - order?: 'append' | 'group'; - /** - * Specifies if the direction of the color variations - */ - direction?: 'lighter' | 'darker' | 'both'; - /** - * Use the default sort order, or re-sort them based on the color wheel (natural) - */ - sortBy?: 'default' | 'natural'; - /** - * Shift the sorting order by a certain number when used in conjunction with `'natural'` `sortBy`. - * Defaults to a number close to green. - */ - sortShift?: string; -}; - -export type EuiPaletteColorBlindProps = EuiPaletteCommonProps & - EuiPaletteRotationProps; - -/** - * For usage in React use the `useEuiPaletteColorBlind` hook instead. - * - * NOTE: These functions rely on base vis colors of the theme which are provided via a global - * singleton instance `EUI_VIS_COLOR_STORE` that's updated by the EuiProvider on theme change. - * Make sure the function is recalled on theme change to retrieve theme-related colors. - * - * Outside of a react component you can use the `subscibe()` method on the `EUI_VIS_COLOR_STORE` - * to subscribe to updates and update your usages to ensure latest colors are loaded. - */ -export const euiPaletteColorBlind = ({ - rotations = 1, - order = 'append', - direction = 'lighter', - sortBy = 'default', - sortShift = '-100', - colors, -}: EuiPaletteColorBlindProps = {}): EuiPalette => { - let _colors: string[] = []; - - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - let base = [ - visColors.euiColorVis0, - visColors.euiColorVis1, - visColors.euiColorVis2, - visColors.euiColorVis3, - visColors.euiColorVis4, - visColors.euiColorVis5, - visColors.euiColorVis6, - visColors.euiColorVis7, - visColors.euiColorVis8, - visColors.euiColorVis9, - ]; - - if (sortBy === 'natural') { - // Sort the colors based on the color wheel, but shifting the values based on sortShift - base = [...base].sort(function (a, b) { - return ( - chroma(a).set('hsl.h', sortShift).hsl()[0] - - chroma(b).set('hsl.h', sortShift).hsl()[0] - ); - }); - } - - if (rotations > 1) { - const palettes = base.map((color) => { - // Create the darkest and lightest versions of each color using black and white - const palette = colorPalette(['black', color, 'white'], 5, false, true); - // Then removing the extremes - palette.pop(); - palette.shift(); - - switch (direction) { - case 'lighter': - return colorPalette([palette[1], palette[2]], rotations, false, true); - case 'darker': - return colorPalette([palette[1], palette[0]], rotations, false, true); - case 'both': - return colorPalette(palette, rotations, false, true); - } - }); - - if (order === 'group') { - _colors = flatten(palettes); - } else { - for (let i = 0; i < rotations; i++) { - const rotation = palettes.map((palette) => palette[i]); - _colors.push(...rotation); - } - } - } else { - _colors = base; - } - - return _colors; -}; - -/** - * For usage in React use the `useEuiPaletteColorBlindBehindText` hook instead. - * - * Color blind palette with text is meant for use when text is applied on top of the color. - * It increases the brightness of the color to give the text more contrast. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteColorBlindBehindText = ( - paletteProps: EuiPaletteColorBlindProps = {} -) => { - const originalPalette = euiPaletteColorBlind(paletteProps); - - return originalPalette; -}; - -/** - * For usage in React use the `useEuiPaletteForStatus` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteForStatus = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisSuccess0]; - } - if (steps <= 3) { - return euiPalette( - [ - visColors.euiColorVisSuccess0, - visColors.euiColorVisWarning1, - visColors.euiColorVisDanger0, - ], - steps, - true - ); - } - return euiPalette( - [ - visColors.euiColorVisSuccess0, - visColors.euiColorVisSuccess1, - visColors.euiColorVisWarning1, - visColors.euiColorVisDanger1, - visColors.euiColorVisDanger0, - ], - steps, - true - ); -}; - -/** - * For usage in React use the `useEuiPaletteForTemperature` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteForTemperature = function ( - steps: any, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisCool2]; - } else if (steps <= 3) { - return euiPalette( - [visColors.euiColorVisCool2, visColors.euiColorVisWarm2], - steps, - true - ); - } - - const cools = colorPalette( - [ - visColors.euiColorVisCool2, - visColors.euiColorVisCool1, - visColors.euiColorVisCool0, - ], - 3 - ); - const warms = colorPalette( - [ - visColors.euiColorVisWarm0, - visColors.euiColorVisWarm1, - visColors.euiColorVisWarm2, - ], - 3 - ); - - const paletteColors = [...cools, visColors.euiColorVisBase0, ...warms]; - - return euiPalette(paletteColors, steps, true); -}; - -/** - * For usage in React use the `useEuiPaletteComplementary` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteComplementary = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisComplementary0]; - } - - const paletteColors = [ - visColors.euiColorVisComplementary0, - visColors.euiColorVisBase0, - visColors.euiColorVisComplementary1, - ]; - - return euiPalette(paletteColors, steps, true); -}; - -/** - * For usage in React use the `useEuiPaletteRed` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteRed = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisDanger1]; - } - - return euiPalette( - [visColors.euiColorVisBase0, visColors.euiColorVisDanger0], - steps - ); -}; - -/** - * For usage in React use the `useEuiPaletteGreen` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteGreen = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisSuccess1]; - } - - return euiPalette( - [visColors.euiColorVisBase0, visColors.euiColorVisSuccess0], - steps - ); -}; - -/** - * For usage in React use the `useEuiPaletteSkyBlue` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteSkyBlue = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisNeutral1]; - } - - return euiPalette( - [visColors.euiColorVisBase0, visColors.euiColorVisNeutral0], - steps - ); -}; - -/** - * For usage in React use the `useEuiPaletteYellow` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteYellow = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisWarning1]; - } - - return euiPalette( - [visColors.euiColorVisBase0, visColors.euiColorVisWarning0], - steps - ); -}; - -/** - * For usage in React use the `useEuiPaletteOrange` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteOrange = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisRisk1]; - } - - return euiPalette( - [visColors.euiColorVisBase0, visColors.euiColorVisRisk0], - steps - ); -}; - -/** - * For usage in React use the `useEuiPaletteCool` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteCool = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisCool1]; - } - - return euiPalette( - [ - visColors.euiColorVisCool0, - visColors.euiColorVisCool1, - visColors.euiColorVisCool2, - ], - steps - ); -}; - -/** - * For usage in React use the `useEuiPaletteWarm` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteWarm = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisWarm2]; - } - - return euiPalette( - [ - visColors.euiColorVisWarm0, - visColors.euiColorVisWarm1, - visColors.euiColorVisWarm2, - ], - steps - ); -}; - -/** - * For usage in React use the `useEuiPaletteGray` hook instead. - * - * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the - * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. - */ -export const euiPaletteGray = function ( - steps: number, - { colors }: EuiPaletteCommonProps = {} -): EuiPalette { - const visColors = colors ?? EUI_VIS_COLOR_STORE.visColors; - - if (steps === 1) { - return [visColors.euiColorVisGrey1]; - } - - return euiPalette( - [ - visColors.euiColorVisBase0, - visColors.euiColorVisGrey0, - visColors.euiColorVisGrey1, - visColors.euiColorVisGrey2, - visColors.euiColorVisGrey3, - ], - steps, - false - ); -}; +// The implementation lives in @elastic/eui-theme-common so that color palettes +// are usable without importing @elastic/eui; re-exported here for backwards +// compatibility. +export { + euiPaletteColorBlind, + euiPaletteColorBlindBehindText, + euiPaletteForStatus, + euiPaletteForTemperature, + euiPaletteComplementary, + euiPaletteRed, + euiPaletteGreen, + euiPaletteSkyBlue, + euiPaletteYellow, + euiPaletteOrange, + euiPaletteCool, + euiPaletteWarm, + euiPaletteGray, + type EuiPalette, + type EuiPaletteColorBlindProps, + type EuiPaletteRotationProps, + type EuiPaletteCommonProps, +} from '@elastic/eui-theme-common'; From 81c4dbcc691bc216969c9cace53fde24102180db Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 27 Oct 2026 18:48:40 +0100 Subject: [PATCH 02/12] Add changelog entry Co-Authored-By: Claude Fable 5 --- packages/eui/changelogs/upcoming/10247.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/eui/changelogs/upcoming/10247.md diff --git a/packages/eui/changelogs/upcoming/10247.md b/packages/eui/changelogs/upcoming/10247.md new file mode 100644 index 000000000000..039535bbb275 --- /dev/null +++ b/packages/eui/changelogs/upcoming/10247.md @@ -0,0 +1 @@ +- Moved `colorPalette` and the `euiPalette*` functions to `@elastic/eui-theme-common`, making them individually importable without loading `@elastic/eui`. They remain re-exported from `@elastic/eui` unchanged. `@elastic/eui-theme-borealis` now initializes the global vis color store on import, and `EuiVisColorStore.getInstance()` throws a descriptive error when accessed before any theme package initialized it. From 636dae78dfa57a114cce932f04e5dd1db9e3525c Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:12:23 +0100 Subject: [PATCH 03/12] Update changelog for colorPalette and euiPalette changes --- packages/eui/changelogs/upcoming/10247.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eui/changelogs/upcoming/10247.md b/packages/eui/changelogs/upcoming/10247.md index 039535bbb275..c6778cf24502 100644 --- a/packages/eui/changelogs/upcoming/10247.md +++ b/packages/eui/changelogs/upcoming/10247.md @@ -1 +1 @@ -- Moved `colorPalette` and the `euiPalette*` functions to `@elastic/eui-theme-common`, making them individually importable without loading `@elastic/eui`. They remain re-exported from `@elastic/eui` unchanged. `@elastic/eui-theme-borealis` now initializes the global vis color store on import, and `EuiVisColorStore.getInstance()` throws a descriptive error when accessed before any theme package initialized it. +- Moved `colorPalette` and the `euiPalette*` functions to `@elastic/eui-theme-common`, making them individually importable without loading `@elastic/eui`. They are re-exported from `@elastic/eui`. `@elastic/eui-theme-borealis` now initializes the global vis color store on import, and `EuiVisColorStore.getInstance()` throws a descriptive error when accessed before any theme package initialized it. From 5ac9466f1577dff472b1f7913aceb15ed1a8450b Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Wed, 28 Oct 2026 14:18:09 +0100 Subject: [PATCH 04/12] Add theme package changelog entries Co-Authored-By: Claude Fable 5 --- packages/eui-theme-borealis/changelogs/upcoming/10247.md | 1 + packages/eui-theme-common/changelogs/upcoming/10247.md | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 packages/eui-theme-borealis/changelogs/upcoming/10247.md create mode 100644 packages/eui-theme-common/changelogs/upcoming/10247.md diff --git a/packages/eui-theme-borealis/changelogs/upcoming/10247.md b/packages/eui-theme-borealis/changelogs/upcoming/10247.md new file mode 100644 index 000000000000..489ab48a55b1 --- /dev/null +++ b/packages/eui-theme-borealis/changelogs/upcoming/10247.md @@ -0,0 +1 @@ +- Updated the package to initialize the global vis color store with its default vis colors on import, so the palette functions in `@elastic/eui-theme-common` work without loading `@elastic/eui` diff --git a/packages/eui-theme-common/changelogs/upcoming/10247.md b/packages/eui-theme-common/changelogs/upcoming/10247.md new file mode 100644 index 000000000000..e5164b12d416 --- /dev/null +++ b/packages/eui-theme-common/changelogs/upcoming/10247.md @@ -0,0 +1,2 @@ +- Added `colorPalette` and the `euiPalette*` functions (moved from `@elastic/eui`), making the color palettes importable without loading `@elastic/eui` +- Updated `EuiVisColorStore.getInstance()` to be callable without arguments once initialized; it now throws a descriptive error when accessed before any theme package initialized it From 05015f9fad1aaf3a95cac2701c2065df6d39a0c6 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Wed, 28 Oct 2026 14:52:22 +0100 Subject: [PATCH 05/12] Fix docs build: emit docgen data for @elastic/eui-theme-common The color-palettes docs page renders PropTable from docgen output, which only scanned @elastic/eui/src. With the palette implementations moved to @elastic/eui-theme-common, euiPaletteColorBlind disappeared from dist/services/color and static site generation crashed on /docs/utilities/color-palettes/. eui-docgen now runs a second pass over @elastic/eui-theme-common, emitting under dist/eui-theme-common/, and the page imports the palette definitions from there. Output shape is unchanged (verified: all 13 palette functions emitted with the same displayName/path/extends/props structure). Co-Authored-By: Claude Fable 5 --- packages/eui-docgen/src/main.ts | 41 +++++++++++++++---- .../docs/utilities/color-palettes/index.mdx | 2 +- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/packages/eui-docgen/src/main.ts b/packages/eui-docgen/src/main.ts index 01b07f10cb11..e3bd7ac3d0ad 100644 --- a/packages/eui-docgen/src/main.ts +++ b/packages/eui-docgen/src/main.ts @@ -21,20 +21,24 @@ const getFiles = (euiPath: string): Array => ( glob.sync(path.join(euiPath, 'src/!(test)/**/!(*.spec|*.test|*.stories|*.styles|*.a11y).{ts,tsx}'), { absolute: true }) ); -const main = async () => { - const distPath = path.resolve(__dirname, '../dist'); - const euiPackagePath = path.resolve(__dirname, '../../eui'); - const euiSrcPath = path.join(euiPackagePath, 'src'); +interface ProcessPackageOptions { + name: string; + packagePath: string; + distPath: string; +} - const files = getFiles(euiPackagePath); +const processPackage = async ({ name, packagePath, distPath }: ProcessPackageOptions) => { + const srcPath = path.join(packagePath, 'src'); + + const files = getFiles(packagePath); const program = buildProgram(files); const programProvider = () => program; - console.log('Generating docgen data for @elastic/eui'); + console.log(`Generating docgen data for ${name}`); let componentExtends: Record = {}; const parser = docgen.withCustomConfig( - path.join(euiPackagePath, 'tsconfig.json'), + path.join(packagePath, 'tsconfig.json'), { propFilter: (prop, component) => filterProp(prop, component, componentExtends), @@ -47,7 +51,7 @@ const main = async () => { let i = 0; for (const file of files) { - const fileRelativePath = path.relative(euiSrcPath, file); + const fileRelativePath = path.relative(srcPath, file); componentExtends = {}; @@ -76,6 +80,27 @@ const main = async () => { i++; } + console.log(`\nProcessed ${name}`); +}; + +const main = async () => { + const distPath = path.resolve(__dirname, '../dist'); + + await processPackage({ + name: '@elastic/eui', + packagePath: path.resolve(__dirname, '../../eui'), + distPath, + }); + + // The color palette implementations live in @elastic/eui-theme-common (they + // are importable without @elastic/eui) but are documented on the website, so + // emit their docgen data as well. + await processPackage({ + name: '@elastic/eui-theme-common', + packagePath: path.resolve(__dirname, '../../eui-theme-common'), + distPath: path.join(distPath, 'eui-theme-common'), + }); + console.log('\nšŸŽ‰ Docgen generation done') } diff --git a/packages/website/docs/utilities/color-palettes/index.mdx b/packages/website/docs/utilities/color-palettes/index.mdx index f3947ea43929..3cf7ae1e0532 100644 --- a/packages/website/docs/utilities/color-palettes/index.mdx +++ b/packages/website/docs/utilities/color-palettes/index.mdx @@ -338,6 +338,6 @@ Use the `colorPalette` service to generate a custom, gradiated palette array of ## Props -import docgen from '@elastic/eui-docgen/dist/services/color'; +import docgen from '@elastic/eui-docgen/dist/eui-theme-common/services/eui_palettes.json'; From b830e0d078bbce3b0eb8bdd60ff1de378f141b3f Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Wed, 28 Oct 2026 14:54:41 +0100 Subject: [PATCH 06/12] Revert "Fix docs build: emit docgen data for @elastic/eui-theme-common" This reverts commit 05015f9fad1aaf3a95cac2701c2065df6d39a0c6. --- packages/eui-docgen/src/main.ts | 41 ++++--------------- .../docs/utilities/color-palettes/index.mdx | 2 +- 2 files changed, 9 insertions(+), 34 deletions(-) diff --git a/packages/eui-docgen/src/main.ts b/packages/eui-docgen/src/main.ts index e3bd7ac3d0ad..01b07f10cb11 100644 --- a/packages/eui-docgen/src/main.ts +++ b/packages/eui-docgen/src/main.ts @@ -21,24 +21,20 @@ const getFiles = (euiPath: string): Array => ( glob.sync(path.join(euiPath, 'src/!(test)/**/!(*.spec|*.test|*.stories|*.styles|*.a11y).{ts,tsx}'), { absolute: true }) ); -interface ProcessPackageOptions { - name: string; - packagePath: string; - distPath: string; -} - -const processPackage = async ({ name, packagePath, distPath }: ProcessPackageOptions) => { - const srcPath = path.join(packagePath, 'src'); +const main = async () => { + const distPath = path.resolve(__dirname, '../dist'); + const euiPackagePath = path.resolve(__dirname, '../../eui'); + const euiSrcPath = path.join(euiPackagePath, 'src'); - const files = getFiles(packagePath); + const files = getFiles(euiPackagePath); const program = buildProgram(files); const programProvider = () => program; - console.log(`Generating docgen data for ${name}`); + console.log('Generating docgen data for @elastic/eui'); let componentExtends: Record = {}; const parser = docgen.withCustomConfig( - path.join(packagePath, 'tsconfig.json'), + path.join(euiPackagePath, 'tsconfig.json'), { propFilter: (prop, component) => filterProp(prop, component, componentExtends), @@ -51,7 +47,7 @@ const processPackage = async ({ name, packagePath, distPath }: ProcessPackageOpt let i = 0; for (const file of files) { - const fileRelativePath = path.relative(srcPath, file); + const fileRelativePath = path.relative(euiSrcPath, file); componentExtends = {}; @@ -80,27 +76,6 @@ const processPackage = async ({ name, packagePath, distPath }: ProcessPackageOpt i++; } - console.log(`\nProcessed ${name}`); -}; - -const main = async () => { - const distPath = path.resolve(__dirname, '../dist'); - - await processPackage({ - name: '@elastic/eui', - packagePath: path.resolve(__dirname, '../../eui'), - distPath, - }); - - // The color palette implementations live in @elastic/eui-theme-common (they - // are importable without @elastic/eui) but are documented on the website, so - // emit their docgen data as well. - await processPackage({ - name: '@elastic/eui-theme-common', - packagePath: path.resolve(__dirname, '../../eui-theme-common'), - distPath: path.join(distPath, 'eui-theme-common'), - }); - console.log('\nšŸŽ‰ Docgen generation done') } diff --git a/packages/website/docs/utilities/color-palettes/index.mdx b/packages/website/docs/utilities/color-palettes/index.mdx index 3cf7ae1e0532..f3947ea43929 100644 --- a/packages/website/docs/utilities/color-palettes/index.mdx +++ b/packages/website/docs/utilities/color-palettes/index.mdx @@ -338,6 +338,6 @@ Use the `colorPalette` service to generate a custom, gradiated palette array of ## Props -import docgen from '@elastic/eui-docgen/dist/eui-theme-common/services/eui_palettes.json'; +import docgen from '@elastic/eui-docgen/dist/services/color'; From f66b8aee8e71abdda8e1dc1fbeb7ca99599a11aa Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Wed, 28 Oct 2026 17:04:53 +0100 Subject: [PATCH 07/12] Keep documented palette wrappers in @elastic/eui for docgen The color-palettes docs page renders PropTables from docgen data generated from @elastic/eui/src; bare re-exports of the moved palette functions dropped them from that output and broke the docs build. Wrap the @elastic/eui-theme-common implementations in thin, JSDoc'd functions with the original signatures so docgen keeps emitting them (verified locally: euiPaletteColorBlind present again with all six props). No API change. Co-Authored-By: Claude Fable 5 --- .../eui/src/services/color/eui_palettes.ts | 200 ++++++++++++++++-- 1 file changed, 179 insertions(+), 21 deletions(-) diff --git a/packages/eui/src/services/color/eui_palettes.ts b/packages/eui/src/services/color/eui_palettes.ts index 0f82ff52ca5c..584e5a70a84d 100644 --- a/packages/eui/src/services/color/eui_palettes.ts +++ b/packages/eui/src/services/color/eui_palettes.ts @@ -6,25 +6,183 @@ * Side Public License, v 1. */ -// The implementation lives in @elastic/eui-theme-common so that color palettes -// are usable without importing @elastic/eui; re-exported here for backwards -// compatibility. -export { - euiPaletteColorBlind, - euiPaletteColorBlindBehindText, - euiPaletteForStatus, - euiPaletteForTemperature, - euiPaletteComplementary, - euiPaletteRed, - euiPaletteGreen, - euiPaletteSkyBlue, - euiPaletteYellow, - euiPaletteOrange, - euiPaletteCool, - euiPaletteWarm, - euiPaletteGray, - type EuiPalette, - type EuiPaletteColorBlindProps, - type EuiPaletteRotationProps, - type EuiPaletteCommonProps, +// The implementations live in @elastic/eui-theme-common so that color palettes +// are usable without importing @elastic/eui. The functions are wrapped (rather +// than re-exported) with their original docblocks so the website's docgen data +// keeps being generated from this package. +import { + euiPaletteColorBlind as _euiPaletteColorBlind, + euiPaletteColorBlindBehindText as _euiPaletteColorBlindBehindText, + euiPaletteForStatus as _euiPaletteForStatus, + euiPaletteForTemperature as _euiPaletteForTemperature, + euiPaletteComplementary as _euiPaletteComplementary, + euiPaletteRed as _euiPaletteRed, + euiPaletteGreen as _euiPaletteGreen, + euiPaletteSkyBlue as _euiPaletteSkyBlue, + euiPaletteYellow as _euiPaletteYellow, + euiPaletteOrange as _euiPaletteOrange, + euiPaletteCool as _euiPaletteCool, + euiPaletteWarm as _euiPaletteWarm, + euiPaletteGray as _euiPaletteGray, } from '@elastic/eui-theme-common'; + +export type { + EuiPalette, + EuiPaletteColorBlindProps, + EuiPaletteRotationProps, + EuiPaletteCommonProps, +} from '@elastic/eui-theme-common'; + +import type { + EuiPalette, + EuiPaletteColorBlindProps, + EuiPaletteCommonProps, +} from '@elastic/eui-theme-common'; + +/** + * For usage in React use the `useEuiPaletteColorBlind` hook instead. + * + * NOTE: These functions rely on base vis colors of the theme which are provided via a global + * singleton instance `EUI_VIS_COLOR_STORE` that's updated by the EuiProvider on theme change. + * Make sure the function is recalled on theme change to retrieve theme-related colors. + * + * Outside of a react component you can use the `subscibe()` method on the `EUI_VIS_COLOR_STORE` + * to subscribe to updates and update your usages to ensure latest colors are loaded. + */ +export const euiPaletteColorBlind = ( + props: EuiPaletteColorBlindProps = {} +): EuiPalette => _euiPaletteColorBlind(props); + +/** + * For usage in React use the `useEuiPaletteColorBlindBehindText` hook instead. + * + * Color blind palette with text is meant for use when text is applied on top of the color. + * It increases the brightness of the color to give the text more contrast. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteColorBlindBehindText = ( + paletteProps: EuiPaletteColorBlindProps = {} +): EuiPalette => _euiPaletteColorBlindBehindText(paletteProps); + +/** + * For usage in React use the `useEuiPaletteForStatus` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteForStatus = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteForStatus(steps, props); + +/** + * For usage in React use the `useEuiPaletteForTemperature` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteForTemperature = ( + steps: any, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteForTemperature(steps, props); + +/** + * For usage in React use the `useEuiPaletteComplementary` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteComplementary = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteComplementary(steps, props); + +/** + * For usage in React use the `useEuiPaletteRed` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteRed = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteRed(steps, props); + +/** + * For usage in React use the `useEuiPaletteGreen` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteGreen = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteGreen(steps, props); + +/** + * For usage in React use the `useEuiPaletteSkyBlue` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteSkyBlue = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteSkyBlue(steps, props); + +/** + * For usage in React use the `useEuiPaletteYellow` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteYellow = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteYellow(steps, props); + +/** + * For usage in React use the `useEuiPaletteOrange` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteOrange = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteOrange(steps, props); + +/** + * For usage in React use the `useEuiPaletteCool` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteCool = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteCool(steps, props); + +/** + * For usage in React use the `useEuiPaletteWarm` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteWarm = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteWarm(steps, props); + +/** + * For usage in React use the `useEuiPaletteGray` hook instead. + * + * NOTE: This function is not pure. It relies on `EUI_VIS_COLOR_STORE` which is updated by the + * EuiProvider on theme change. Ensure to recall the function on theme change or subscribe to the store. + */ +export const euiPaletteGray = ( + steps: number, + props: EuiPaletteCommonProps = {} +): EuiPalette => _euiPaletteGray(steps, props); From 6dc13e991748368981bc16f458491ef66b860a28 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:22:38 +0100 Subject: [PATCH 08/12] Update 10247.md --- packages/eui-theme-borealis/changelogs/upcoming/10247.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eui-theme-borealis/changelogs/upcoming/10247.md b/packages/eui-theme-borealis/changelogs/upcoming/10247.md index 489ab48a55b1..5f8ae04edb3e 100644 --- a/packages/eui-theme-borealis/changelogs/upcoming/10247.md +++ b/packages/eui-theme-borealis/changelogs/upcoming/10247.md @@ -1 +1 @@ -- Updated the package to initialize the global vis color store with its default vis colors on import, so the palette functions in `@elastic/eui-theme-common` work without loading `@elastic/eui` +- Initialize the global vis color store with its default vis colors on import, so the palette functions in `@elastic/eui-theme-common` work without loading `@elastic/eui` From d2e23793060dc2d324576629c394610ccd75a9b3 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:23:00 +0100 Subject: [PATCH 09/12] Delete packages/eui-theme-common/src/services/color_palette.test.ts --- .../src/services/color_palette.test.ts | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 packages/eui-theme-common/src/services/color_palette.test.ts diff --git a/packages/eui-theme-common/src/services/color_palette.test.ts b/packages/eui-theme-common/src/services/color_palette.test.ts deleted file mode 100644 index ef1225dac88c..000000000000 --- a/packages/eui-theme-common/src/services/color_palette.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { colorPalette } from './color_palette'; - -describe('colorPalette', () => { - it('should generate the expected palette', () => { - const actualPalette = colorPalette(['#FFFF6D', '#1EA593']); - expect(actualPalette).toEqual([ - '#ffff6d', - '#ecf573', - '#d8eb79', - '#c4e07d', - '#b0d682', - '#9acc86', - '#84c28a', - '#6bb98d', - '#4daf90', - '#1ea593', - ]); - }); - - it('should generate a palette with the specified spread', () => { - const actualPalette = colorPalette(['#FFFF6D', '#1EA593'], 6); - expect(actualPalette).toEqual([ - '#ffff6d', - '#dced77', - '#b8da80', - '#92c887', - '#66b78e', - '#1ea593', - ]); - }); -}); From d03540c0122d295a7ac29a4c4b87809a8dd58a21 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:23:47 +0100 Subject: [PATCH 10/12] Clean up comments in color_palette.ts Removed comments about the implementation location and re-export. --- packages/eui/src/services/color/color_palette.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/eui/src/services/color/color_palette.ts b/packages/eui/src/services/color/color_palette.ts index 008bfb9ce3c6..f7fca5661f3f 100644 --- a/packages/eui/src/services/color/color_palette.ts +++ b/packages/eui/src/services/color/color_palette.ts @@ -6,7 +6,5 @@ * Side Public License, v 1. */ -// The implementation lives in @elastic/eui-theme-common so that color palettes -// are usable without importing @elastic/eui; re-exported here for backwards -// compatibility. +// Re-exported here for backwards compatibility. export { MID_COLOR_STOP, colorPalette } from '@elastic/eui-theme-common'; From 544b97009ccc7032cb8b266b6e2d0462ef5f1778 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:08:30 +0100 Subject: [PATCH 11/12] Update eui_palettes.ts --- packages/eui/src/services/color/eui_palettes.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/eui/src/services/color/eui_palettes.ts b/packages/eui/src/services/color/eui_palettes.ts index 584e5a70a84d..8162dab3825e 100644 --- a/packages/eui/src/services/color/eui_palettes.ts +++ b/packages/eui/src/services/color/eui_palettes.ts @@ -6,10 +6,8 @@ * Side Public License, v 1. */ -// The implementations live in @elastic/eui-theme-common so that color palettes -// are usable without importing @elastic/eui. The functions are wrapped (rather -// than re-exported) with their original docblocks so the website's docgen data -// keeps being generated from this package. +// The functions are wrapped with their original docblocks so the docgen data +// is generated from this package. import { euiPaletteColorBlind as _euiPaletteColorBlind, euiPaletteColorBlindBehindText as _euiPaletteColorBlindBehindText, From 8a4c6f294279c1cc3deaf88e53852e7cffa8f0fe Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Thu, 29 Oct 2026 17:46:55 +0100 Subject: [PATCH 12/12] Address review: restore store init on deep imports, test init contract - @elastic/eui eui_palettes.ts side-effect-imports ./vis_color_store again, so a standalone deep import of the module (and the eager VISUALIZATION_COLORS evaluation in services/color) initializes the vis color store with theme defaults exactly as before the palette move. - Add vis color store tests for the new initialization contract: getInstance() before init throws descriptively, no-argument getInstance() after init returns the existing instance, palettes resolve default colors once a theme initializes the store, and palette calls without any theme loaded throw. Co-Authored-By: Claude Fable 5 --- .../src/services/vis_color_store.test.ts | 60 +++++++++++++++++++ .../eui/src/services/color/eui_palettes.ts | 4 ++ 2 files changed, 64 insertions(+) diff --git a/packages/eui-theme-common/src/services/vis_color_store.test.ts b/packages/eui-theme-common/src/services/vis_color_store.test.ts index 5e1a31e7a5e3..1cf13a73ba04 100644 --- a/packages/eui-theme-common/src/services/vis_color_store.test.ts +++ b/packages/eui-theme-common/src/services/vis_color_store.test.ts @@ -87,3 +87,63 @@ describe('EuiVisColorStore', () => { }); }); }); + +describe('getInstance() initialization contract', () => { + // Each test gets a fresh module registry so the singleton starts + // uninitialized, as it would for a consumer that imports this package + // standalone (e.g. without @elastic/eui). + it('throws when accessed before being initialized with default colors', () => { + jest.isolateModules(() => { + const { EuiVisColorStore: FreshStore } = + jest.requireActual( + './vis_color_store' + ); + + expect(() => FreshStore.getInstance()).toThrow( + 'EuiVisColorStore was accessed before it was initialized' + ); + }); + }); + + it('returns the existing instance when called without arguments after initialization', () => { + jest.isolateModules(() => { + const { EuiVisColorStore: FreshStore } = + jest.requireActual( + './vis_color_store' + ); + + const initialized = FreshStore.getInstance(visColors); + + expect(FreshStore.getInstance()).toBe(initialized); + expect(FreshStore.getInstance().visColors).toEqual(visColors); + }); + }); + + it('provides palette defaults once a theme package has initialized the store', () => { + jest.isolateModules(() => { + const { EuiVisColorStore: FreshStore } = + jest.requireActual( + './vis_color_store' + ); + const { euiPaletteColorBlind } = + jest.requireActual('./eui_palettes'); + + // simulates what a theme package (e.g. @elastic/eui-theme-borealis) + // does on import + FreshStore.getInstance(visColors); + + expect(euiPaletteColorBlind()[0]).toBe(visColors.euiColorVis0); + }); + }); + + it('palette functions throw a descriptive error when no theme initialized the store', () => { + jest.isolateModules(() => { + const { euiPaletteColorBlind } = + jest.requireActual('./eui_palettes'); + + expect(() => euiPaletteColorBlind()).toThrow( + 'EuiVisColorStore was accessed before it was initialized' + ); + }); + }); +}); diff --git a/packages/eui/src/services/color/eui_palettes.ts b/packages/eui/src/services/color/eui_palettes.ts index 8162dab3825e..2227b2fb952a 100644 --- a/packages/eui/src/services/color/eui_palettes.ts +++ b/packages/eui/src/services/color/eui_palettes.ts @@ -6,6 +6,10 @@ * Side Public License, v 1. */ +// Initializes the vis color store with theme defaults, as importing this +// module always has; without it a standalone deep import of this module (or +// evaluating VISUALIZATION_COLORS before a theme loads) would throw. +import './vis_color_store'; // The functions are wrapped with their original docblocks so the docgen data // is generated from this package. import {