From 37180039a77d908acd731c89067ecfce3f23955f Mon Sep 17 00:00:00 2001
From: Nantawat Poothong <102957966+Nantawat-Poothong@users.noreply.github.com>
Date: Wed, 31 Aug 2022 14:47:43 +0700
Subject: [PATCH] feat(color-picker): add color picker (#444)
* feat(color-picker): add color picker
* fix(color-dialog): import test path
* chore(color-picker): update code style
* fix(color-picker): update snapshot ignore style
* fix(color-picker): add missing end tag
* chore(color-picker): rename test file
---
documents/src/pages/elements/color-picker.md | 56 ++++
.../src/custom-elements/ef-color-picker.less | 33 +++
packages/elements/package.json | 5 +
.../__test__/color-dialog.test.js | 4 +-
.../__test__/color-helpers.test.js | 2 +-
.../src/color-dialog/elements/palettes.ts | 3 +-
.../src/color-dialog/helpers/color-helpers.ts | 17 --
.../src/color-dialog/helpers/value-model.ts | 3 +-
packages/elements/src/color-dialog/index.ts | 3 +-
.../src/color-picker/__demo__/index.html | 65 +++++
.../color-picker/__snapshots__/ColorPicker.md | 42 +++
.../__test__/color-picker.test.js | 114 ++++++++
packages/elements/src/color-picker/index.ts | 252 ++++++++++++++++++
.../src/custom-elements/ef-color-picker.less | 7 +
packages/phrasebook/package.json | 5 +
.../phrasebook/src/locale/de/color-picker.ts | 11 +
.../phrasebook/src/locale/en/color-picker.ts | 11 +
.../phrasebook/src/locale/ja/color-picker.ts | 11 +
.../src/locale/zh-hant/color-picker.ts | 11 +
.../phrasebook/src/locale/zh/color-picker.ts | 11 +
.../src/custom-elements/ef-color-picker.less | 7 +
packages/utils/src/color.ts | 1 +
packages/utils/src/color/color.ts | 16 ++
23 files changed, 664 insertions(+), 26 deletions(-)
create mode 100644 documents/src/pages/elements/color-picker.md
create mode 100644 packages/elemental-theme/src/custom-elements/ef-color-picker.less
create mode 100644 packages/elements/src/color-picker/__demo__/index.html
create mode 100644 packages/elements/src/color-picker/__snapshots__/ColorPicker.md
create mode 100644 packages/elements/src/color-picker/__test__/color-picker.test.js
create mode 100644 packages/elements/src/color-picker/index.ts
create mode 100644 packages/halo-theme/src/custom-elements/ef-color-picker.less
create mode 100644 packages/phrasebook/src/locale/de/color-picker.ts
create mode 100644 packages/phrasebook/src/locale/en/color-picker.ts
create mode 100644 packages/phrasebook/src/locale/ja/color-picker.ts
create mode 100644 packages/phrasebook/src/locale/zh-hant/color-picker.ts
create mode 100644 packages/phrasebook/src/locale/zh/color-picker.ts
create mode 100644 packages/solar-theme/src/custom-elements/ef-color-picker.less
create mode 100644 packages/utils/src/color/color.ts
diff --git a/documents/src/pages/elements/color-picker.md b/documents/src/pages/elements/color-picker.md
new file mode 100644
index 0000000000..18454ac4f1
--- /dev/null
+++ b/documents/src/pages/elements/color-picker.md
@@ -0,0 +1,56 @@
+
+
+# Color Picker
+::
+```javascript
+::color-picker::
+```
+```css
+
+```
+```html
+
+```
+::
+
+`ef-color-picker` allows users to pick any colours from colour dialog.
+
+### Basic usage
+You can set an initial value via `value` attribute. The `value` must be a string of hex colour code.
+
+```html
+
+```
+
+### Getting value
+A value of Color picker can be accessed through `value` property. It will fire `value-changed` event when users picked a new colour. `value` will be an empty string if users choose No Color.
+
+### 'No Color' option
+In some circumstances, it might be necessary that the component should allow user to select "no color". This can be done by using a property/attribute `allow-nocolor` to activate this feature.
+
+Color picker will set attribute/property `value` to `""` when users select no-color from the colour dialog.
+
+```html
+
+```
diff --git a/packages/elemental-theme/src/custom-elements/ef-color-picker.less b/packages/elemental-theme/src/custom-elements/ef-color-picker.less
new file mode 100644
index 0000000000..f6b46685b1
--- /dev/null
+++ b/packages/elemental-theme/src/custom-elements/ef-color-picker.less
@@ -0,0 +1,33 @@
+
+@import 'element:ef-color-dialog';
+@import '../responsive';
+
+:host {
+ @color-picker-size: @button-height;
+ @color-picker-border: @control-border-color;
+ @color-picker-focused-border-color: @scheme-color-primary;
+
+ outline: none;
+ box-sizing: border-box;
+ height: @color-picker-size;
+ min-width: @color-picker-size;
+ border: 1px solid @color-picker-border;
+
+ &[disabled], &[readonly] {
+ border-color: @input-disabled-border-color;
+ }
+
+ [part="color-item"] {
+ cursor: pointer;
+ height: 100%;
+ }
+
+ &[disabled] [part="color-item"],
+ &[readonly] [part="color-item"] {
+ pointer-events: none;
+ }
+
+ &[focused=visible] {
+ border-color: @color-picker-focused-border-color;
+ }
+}
\ No newline at end of file
diff --git a/packages/elements/package.json b/packages/elements/package.json
index 38815b0f69..6e71645f3c 100644
--- a/packages/elements/package.json
+++ b/packages/elements/package.json
@@ -91,6 +91,11 @@
"./color-dialog/themes/halo/light": "./lib/color-dialog/themes/halo/light/index.js",
"./color-dialog/themes/solar/charcoal": "./lib/color-dialog/themes/solar/charcoal/index.js",
"./color-dialog/themes/solar/pearl": "./lib/color-dialog/themes/solar/pearl/index.js",
+ "./color-picker": "./lib/color-picker/index.js",
+ "./color-picker/themes/halo/dark": "./lib/color-picker/themes/halo/dark/index.js",
+ "./color-picker/themes/halo/light": "./lib/color-picker/themes/halo/light/index.js",
+ "./color-picker/themes/solar/charcoal": "./lib/color-picker/themes/solar/charcoal/index.js",
+ "./color-picker/themes/solar/pearl": "./lib/color-picker/themes/solar/pearl/index.js",
"./combo-box": "./lib/combo-box/index.js",
"./combo-box/themes/halo/dark": "./lib/combo-box/themes/halo/dark/index.js",
"./combo-box/themes/halo/light": "./lib/combo-box/themes/halo/light/index.js",
diff --git a/packages/elements/src/color-dialog/__test__/color-dialog.test.js b/packages/elements/src/color-dialog/__test__/color-dialog.test.js
index 1987cfbc07..e0a45b69a6 100644
--- a/packages/elements/src/color-dialog/__test__/color-dialog.test.js
+++ b/packages/elements/src/color-dialog/__test__/color-dialog.test.js
@@ -5,8 +5,8 @@ import '@refinitiv-ui/elements/color-dialog';
import '@refinitiv-ui/elemental-theme/light/ef-color-dialog';
import '@refinitiv-ui/elemental-theme/light/ef-text-field';
import '@refinitiv-ui/elemental-theme/light/ef-number-field';
-import { rgb } from '@refinitiv-ui/utils';
-import { COLOR_ITEMS, removeHashSign } from '../../../lib/color-dialog/helpers/color-helpers';
+import { rgb, removeHashSign } from '@refinitiv-ui/utils/color.js';
+import { COLOR_ITEMS } from '../../../lib/color-dialog/helpers/color-helpers';
describe('color-dialog/ColorDialog', () => {
describe('Default Color Dialog', () => {
diff --git a/packages/elements/src/color-dialog/__test__/color-helpers.test.js b/packages/elements/src/color-dialog/__test__/color-helpers.test.js
index 48358ac28d..6192222efb 100644
--- a/packages/elements/src/color-dialog/__test__/color-helpers.test.js
+++ b/packages/elements/src/color-dialog/__test__/color-helpers.test.js
@@ -1,4 +1,4 @@
-import { isHex } from '../../../lib/color-dialog/helpers/color-helpers.js';
+import { isHex } from '@refinitiv-ui/utils/color.js';
import { expect } from '@refinitiv-ui/test-helpers';
describe('color-dialog/Helpers', () => {
diff --git a/packages/elements/src/color-dialog/elements/palettes.ts b/packages/elements/src/color-dialog/elements/palettes.ts
index be1460a24c..d668ee67f8 100644
--- a/packages/elements/src/color-dialog/elements/palettes.ts
+++ b/packages/elements/src/color-dialog/elements/palettes.ts
@@ -6,8 +6,7 @@ import {
import { property } from '@refinitiv-ui/core/decorators/property.js';
import { query } from '@refinitiv-ui/core/decorators/query.js';
import { VERSION } from '../../version.js';
-import { rgb } from '@refinitiv-ui/utils/color.js';
-import { isHex } from '../helpers/color-helpers.js';
+import { rgb, isHex } from '@refinitiv-ui/utils/color.js';
/**
* Element base class usually used
diff --git a/packages/elements/src/color-dialog/helpers/color-helpers.ts b/packages/elements/src/color-dialog/helpers/color-helpers.ts
index 442398035e..5ca173857a 100644
--- a/packages/elements/src/color-dialog/helpers/color-helpers.ts
+++ b/packages/elements/src/color-dialog/helpers/color-helpers.ts
@@ -146,20 +146,3 @@ export const GRAYSCALE_ITEMS = [
];
export const NOCOLOR_POINTS = '6, 2, 15, 6, 15, 17, 6, 21, -3, 17, -3, 6';
-
-const HEX_REGEXP = /^#([0-9A-F]{3}){1,2}$/i; // used to validate HEX
-export const isHex = (value: string): boolean => HEX_REGEXP.test(value);
-
-/**
- * Remove hash (#) sign from hex value
- * @param hex Hex to check
- * @returns hex value without # sign
- */
-export const removeHashSign = (hex: string): string => {
- if (hex) {
- if (hex.startsWith('#')) {
- hex = hex.slice(1);
- }
- }
- return hex;
-};
diff --git a/packages/elements/src/color-dialog/helpers/value-model.ts b/packages/elements/src/color-dialog/helpers/value-model.ts
index 774c8f7495..5a4e18d243 100644
--- a/packages/elements/src/color-dialog/helpers/value-model.ts
+++ b/packages/elements/src/color-dialog/helpers/value-model.ts
@@ -1,5 +1,4 @@
-import { rgb } from '@refinitiv-ui/utils/color.js';
-import { isHex } from './color-helpers.js';
+import { rgb, isHex } from '@refinitiv-ui/utils/color.js';
const rgbNumberToString = (value: number): string => isNaN(value) ? '' : `${value}`; // replace NaN with empty string
diff --git a/packages/elements/src/color-dialog/index.ts b/packages/elements/src/color-dialog/index.ts
index 0be7c4ec37..f8cc22846b 100644
--- a/packages/elements/src/color-dialog/index.ts
+++ b/packages/elements/src/color-dialog/index.ts
@@ -10,13 +10,12 @@ import { customElement } from '@refinitiv-ui/core/decorators/custom-element.js';
import { property } from '@refinitiv-ui/core/decorators/property.js';
import { query } from '@refinitiv-ui/core/decorators/query.js';
import { styleMap } from '@refinitiv-ui/core/directives/style-map.js';
-import { rgb } from '@refinitiv-ui/utils/color.js';
+import { rgb, isHex, removeHashSign } from '@refinitiv-ui/utils/color.js';
import { VERSION } from '../version.js';
import type { NumberField } from '../number-field';
import type { TextField } from '../text-field';
import type { Palettes } from './elements/palettes';
import { ValueModel } from './helpers/value-model.js';
-import { isHex, removeHashSign } from './helpers/color-helpers.js';
import '../button/index.js';
import '../number-field/index.js';
import '../text-field/index.js';
diff --git a/packages/elements/src/color-picker/__demo__/index.html b/packages/elements/src/color-picker/__demo__/index.html
new file mode 100644
index 0000000000..625b1cf3d5
--- /dev/null
+++ b/packages/elements/src/color-picker/__demo__/index.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+ Color Picker
+
+
+
+
+
+
+
+
+
+ Default Hex Value (Optional):
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/elements/src/color-picker/__snapshots__/ColorPicker.md b/packages/elements/src/color-picker/__snapshots__/ColorPicker.md
new file mode 100644
index 0000000000..096d56590c
--- /dev/null
+++ b/packages/elements/src/color-picker/__snapshots__/ColorPicker.md
@@ -0,0 +1,42 @@
+# `color-picker/ColorPicker`
+
+## `DOM structure`
+
+#### `DOM structure is correct`
+
+```html
+
+
+
+```
+
+#### `DOM structure is correct when opened`
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
diff --git a/packages/elements/src/color-picker/__test__/color-picker.test.js b/packages/elements/src/color-picker/__test__/color-picker.test.js
new file mode 100644
index 0000000000..dfbe9533cf
--- /dev/null
+++ b/packages/elements/src/color-picker/__test__/color-picker.test.js
@@ -0,0 +1,114 @@
+import { fixture, expect, elementUpdated, keyboardEvent, oneEvent } from '@refinitiv-ui/test-helpers';
+// import element and theme
+import '@refinitiv-ui/elements/color-picker';
+import '@refinitiv-ui/elemental-theme/light/ef-color-picker';
+
+describe('color-picker/ColorPicker', () => {
+
+ describe('DOM structure', () => {
+ it('DOM structure is correct', async () => {
+ const el = await fixture('');
+ expect(el).shadowDom.to.equalSnapshot();
+ });
+ it('DOM structure is correct when opened', async () => {
+ const el = await fixture('');
+ expect(el).shadowDom.to.equalSnapshot({ ignoreAttributes: ['class', 'style'] });
+ });
+ });
+
+ describe('Value property', () => {
+ it('Should have default value', async () => {
+ const el = await fixture('');
+ expect(el.value).to.equal('');
+ });
+ it('Should update value when set hex color', async () => {
+ const el = await fixture('');
+ expect(el.value).to.equal('#001EFF');
+ });
+ it('Should reset to default value when value is invalid', async () => {
+ const el = await fixture('');
+ expect(el.value).to.equal('');
+ });
+ it("Should not fires value-changed event when programmatically changes value", async () => {
+ const value = '#001EFF';
+ const el = await fixture('');
+
+ let eventFired = false;
+ el.addEventListener('value-changed', () => {
+ eventFired = true;
+ });
+ el.value = value;
+ expect(el.value).to.equal(value);
+ expect(eventFired).to.equal(false);
+ });
+ it('Should fires value-changed event when value change by user interactions', async () => {
+ const el = await fixture('');
+ const dialogEl = el.dialogEl;
+ const redInput = dialogEl.shadowRoot.getElementById('redInput');
+ const confirmBtn = dialogEl.shadowRoot.getElementById('confirmButton');
+ redInput.value = 200;
+ redInput.dispatchEvent(new Event('value-changed'));
+ await elementUpdated();
+ setTimeout(() => confirmBtn.click());
+ await oneEvent(el, 'value-changed');
+ await elementUpdated();
+ expect(el.value).to.equal('#c81eff');
+ });
+ });
+
+ describe('No color property', () => {
+ it('Should not have allow-nocolor property on color dialog', async () => {
+ const el = await fixture('');
+ el.opened = true;
+ await elementUpdated(el);
+ expect(el.dialogEl.hasAttribute('allow-nocolor')).to.be.equal(false);
+ });
+ it('Should pass allow-nocolor property to color dialog', async () => {
+ const el = await fixture('');
+ el.opened = true;
+ await elementUpdated(el);
+ expect(el.dialogEl.hasAttribute('allow-nocolor')).to.be.equal(true);
+ });
+ });
+
+ describe('Color dialog', () => {
+ it('Should open dialog when click on color picker', async () => {
+ const el = await fixture('');
+ el.click();
+ await elementUpdated(el);
+ expect(el.dialogEl.opened).to.be.equal(true, 'clicking on color picker should open color dialog');
+ });
+ it('Should open dialog when opened programmatically', async () => {
+ const el = await fixture('');
+ el.opened = true;
+ await elementUpdated(el);
+ expect(el.dialogEl.hasAttribute('opened')).to.be.equal(true);
+ });
+ it('Should not open color dialog when disabled', async () => {
+ const el = await fixture('');
+ el.click();
+ expect(el.opened).to.be.equal(false, 'clicking on disabled should do nothing');
+ });
+ it('Should not open color dialog when readonly', async () => {
+ const el = await fixture('');
+ el.click();
+ expect(el.opened).to.be.equal(false, 'clicking on readonly should do nothing');
+ });
+ });
+
+ describe('Navigation', () => {
+ it('Should open dialog when press enter key', async () => {
+ const el = await fixture('');
+ el.dispatchEvent(keyboardEvent('keydown', { key: 'Enter' }));
+ await elementUpdated(el);
+ expect(el.dialogEl.opened).to.be.equal(true, 'Enter should open dialog');
+ });
+ it('Should open dialog when press spacebar key', async () => {
+ const el = await fixture('');
+ el.dispatchEvent(keyboardEvent('keydown', { key: 'Spacebar' }));
+ await elementUpdated(el);
+ expect(el.dialogEl.opened).to.be.equal(true, 'Spacebar should open dialog');
+ });
+ });
+});
+
diff --git a/packages/elements/src/color-picker/index.ts b/packages/elements/src/color-picker/index.ts
new file mode 100644
index 0000000000..cb3c40a7d6
--- /dev/null
+++ b/packages/elements/src/color-picker/index.ts
@@ -0,0 +1,252 @@
+import {
+ ControlElement,
+ html,
+ css,
+ PropertyValues,
+ TemplateResult,
+ TapEvent,
+ CSSResult,
+ WarningNotice
+} from '@refinitiv-ui/core';
+import { customElement } from '@refinitiv-ui/core/decorators/custom-element.js';
+import type { OpenedChangedEvent, ValueChangedEvent } from '../events';
+import { property } from '@refinitiv-ui/core/decorators/property.js';
+import { query } from '@refinitiv-ui/core/decorators/query.js';
+import { styleMap } from '@refinitiv-ui/core/directives/style-map.js';
+import { ifDefined } from '@refinitiv-ui/core/directives/if-defined.js';
+import { VERSION } from '../version.js';
+import { isHex } from '@refinitiv-ui/utils/color.js';
+import '../color-dialog/index.js';
+import type { ColorDialog } from '../color-dialog/index.js';
+import '@refinitiv-ui/phrasebook/locale/en/color-picker.js';
+
+const DIALOG_POSITION = ['right-start', 'right-end', 'right-middle', 'left-start', 'left-end', 'left-middle'];
+
+/**
+ *
+ * Color picker control
+ * @fires value-changed - Dispatched when value changes
+ *
+ * @attr {boolean} readonly - Set readonly state
+ * @prop {boolean} [readonly=false] - Set readonly state
+ *
+ * @attr {boolean} disabled - Set disabled state
+ * @prop {boolean} [disabled=false] - Set disabled state
+ */
+@customElement('ef-color-picker', {
+ alias: 'emerald-color-picker'
+})
+export class ColorPicker extends ControlElement {
+ /**
+ * Element version number
+ * @returns version number
+ */
+ static get version (): string {
+ return VERSION;
+ }
+
+ /**
+ * Set the color dialog to activate no-color option
+ */
+ @property({ type: Boolean, attribute: 'allow-nocolor' })
+ public allowNocolor = false;
+
+ /**
+ * Set lang to color dialog
+ * @ignore
+ */
+ @property({ type: String })
+ public lang = '';
+
+ /**
+ * A `CSSResult` that will be used
+ * to style the host, slotted children
+ * and the internal template of the element.
+ * @returns CSS template
+ */
+ static get styles (): CSSResult | CSSResult[] {
+ return css`
+ :host {
+ display: inline-block;
+ }
+ [part=color-item][no-color] {
+ background: linear-gradient(to bottom right, transparent calc(50% - 1px),
+ var(--no-color-line-color, #ff0000) calc(50% - 1px),
+ var(--no-color-line-color, #ff0000) calc(50% + 1px),
+ transparent calc(50% + 1px));
+ }
+ `;
+ }
+
+ private lazyRendered = false; /* speed up rendering by not populating color dialog on first load */
+
+ /**
+ * Toggles the opened state of the dialog
+ */
+ @property({ type: Boolean, reflect: true })
+ public opened = false;
+
+ @query('[part=dialog]') private dialogEl?: ColorDialog | null;
+
+ /**
+ * Check if value is valid HEX value (including #)
+ * @param value Value to check
+ * @returns true if value is valid
+ */
+ protected isValidValue (value: string): boolean {
+ return value === '' || isHex(value);
+ }
+
+ /**
+ * Used to show a warning when the value does not pass the validation
+ * @param value that is invalid
+ * @returns {void}
+ */
+ protected warnInvalidValue (value: string): void {
+ new WarningNotice(`The specified value "${value}" is not valid value. The correct value should look like "#fff" or "#ffffff".`).show();
+ }
+
+ /**
+ * Return true if popup can be opened
+ */
+ private get canOpenPopup (): boolean {
+ return !(this.disabled || this.readonly);
+ }
+
+ /**
+ * Called after the component is first rendered
+ * @param changedProperties Properties which have changed
+ * @returns {void}
+ */
+ protected firstUpdated (changedProperties: PropertyValues): void {
+ super.firstUpdated(changedProperties);
+ this.addEventListener('tap', this.onTap);
+ this.addEventListener('keydown', this.onKeyDown);
+ }
+
+ /**
+ * Updates the element
+ * @param changedProperties Properties that has changed
+ * @returns {void}
+ */
+ protected update (changedProperties: PropertyValues): void {
+ if (changedProperties.has('opened') && this.opened) {
+ this.lazyRendered = true;
+ }
+ // make sure to close dialog for disabled
+ if (this.opened && !this.canOpenPopup) {
+ this.opened = false; /* this cannot be nor stopped nor listened */
+ }
+
+ super.update(changedProperties);
+ }
+
+ /**
+ * Run on tap event
+ * @param event Tap event
+ * @returns {void}
+ */
+ private onTap (event: TapEvent): void {
+ const path = event.composedPath();
+ if ((this.dialogEl && path.includes(this.dialogEl)) || event.defaultPrevented) {
+ return; /* dialog is managed separately */
+ }
+ this.setOpened(!this.opened);
+ }
+
+ /**
+ * Handles key input on color picker
+ * @param event Key down event object
+ * @returns {void}
+ */
+ private onKeyDown (event: KeyboardEvent): void {
+ if (event.defaultPrevented) {
+ return;
+ }
+ switch (event.key) {
+ case 'Enter':
+ case ' ':
+ case 'Spacebar':
+ this.setOpened(true);
+ break;
+ default:
+ return;
+ }
+ event.preventDefault();
+ }
+
+ /**
+ * Set opened state with event
+ * @param opened True if opened
+ * @returns {void}
+ */
+ private setOpened (opened: boolean): void {
+ if (opened && !this.canOpenPopup) { /* never allow to open popup if cannot do so */
+ return;
+ }
+ if (this.opened !== opened) {
+ this.opened = opened;
+ }
+ }
+
+ /**
+ * Run on color dialog value-changed event
+ * @param event value-changed event
+ * @returns {void}
+ */
+ private onColorDialogValueChanged (event: ValueChangedEvent): void {
+ const value = event.detail.value;
+ this.value = value;
+ this.setAttribute('value', this.value);
+ this.notifyPropertyChange('value', this.value);
+ this.setOpened(false);
+ }
+
+ /**
+ * Run on color dialog opened-changed event
+ * @param event opened-changed event
+ * @returns {void}
+ */
+ private onColorDialogOpenedChanged (event: OpenedChangedEvent): void {
+ this.setOpened(event.detail.value);
+ }
+
+ /**
+ * Color dialog template
+ */
+ private get dialogTemplate (): TemplateResult | undefined {
+ if (this.lazyRendered) {
+ return html``;
+ }
+ }
+
+ /**
+ * Color item template
+ */
+ private get colorItemTemplate (): TemplateResult | undefined {
+ return html``;
+ }
+
+ /**
+ * A `TemplateResult` that will be used
+ * to render the updated internal template.
+ * @return Render template
+ */
+ protected render (): TemplateResult {
+ return html`
+ ${this.colorItemTemplate}
+ ${this.dialogTemplate}
+ `;
+ }
+}
diff --git a/packages/halo-theme/src/custom-elements/ef-color-picker.less b/packages/halo-theme/src/custom-elements/ef-color-picker.less
new file mode 100644
index 0000000000..ce9740c1f2
--- /dev/null
+++ b/packages/halo-theme/src/custom-elements/ef-color-picker.less
@@ -0,0 +1,7 @@
+@import '@refinitiv-ui/elemental-theme/src/custom-elements/ef-color-picker';
+
+:host {
+ &:not([readonly]):not([disabled]):not([focused]):hover {
+ border-color: @input-hover-border-color;
+ }
+}
diff --git a/packages/phrasebook/package.json b/packages/phrasebook/package.json
index 150f7c1ecf..d796a42a8f 100644
--- a/packages/phrasebook/package.json
+++ b/packages/phrasebook/package.json
@@ -26,6 +26,7 @@
"./locale/de/calendar.js": "./lib/locale/de/calendar.js",
"./locale/de/clock.js": "./lib/locale/de/clock.js",
"./locale/de/color-dialog.js": "./lib/locale/de/color-dialog.js",
+ "./locale/de/color-picker.js": "./lib/locale/de/color-picker.js",
"./locale/de/combo-box.js": "./lib/locale/de/combo-box.js",
"./locale/de/datetime-field.js": "./lib/locale/de/datetime-field.js",
"./locale/de/dialog.js": "./lib/locale/de/dialog.js",
@@ -43,6 +44,7 @@
"./locale/en/calendar.js": "./lib/locale/en/calendar.js",
"./locale/en/clock.js": "./lib/locale/en/clock.js",
"./locale/en/color-dialog.js": "./lib/locale/en/color-dialog.js",
+ "./locale/en/color-picker.js": "./lib/locale/en/color-picker.js",
"./locale/en/combo-box.js": "./lib/locale/en/combo-box.js",
"./locale/en/datetime-field.js": "./lib/locale/en/datetime-field.js",
"./locale/en/dialog.js": "./lib/locale/en/dialog.js",
@@ -60,6 +62,7 @@
"./locale/ja/calendar.js": "./lib/locale/ja/calendar.js",
"./locale/ja/clock.js": "./lib/locale/ja/clock.js",
"./locale/ja/color-dialog.js": "./lib/locale/ja/color-dialog.js",
+ "./locale/ja/color-picker.js": "./lib/locale/ja/color-picker.js",
"./locale/ja/combo-box.js": "./lib/locale/ja/combo-box.js",
"./locale/ja/datetime-field.js": "./lib/locale/ja/datetime-field.js",
"./locale/ja/dialog.js": "./lib/locale/ja/dialog.js",
@@ -77,6 +80,7 @@
"./locale/zh/calendar.js": "./lib/locale/zh/calendar.js",
"./locale/zh/clock.js": "./lib/locale/zh/clock.js",
"./locale/zh/color-dialog.js": "./lib/locale/zh/color-dialog.js",
+ "./locale/zh/color-picker.js": "./lib/locale/zh/color-picker.js",
"./locale/zh/combo-box.js": "./lib/locale/zh/combo-box.js",
"./locale/zh/datetime-field.js": "./lib/locale/zh/datetime-field.js",
"./locale/zh/dialog.js": "./lib/locale/zh/dialog.js",
@@ -94,6 +98,7 @@
"./locale/zh-hant/calendar.js": "./lib/locale/zh-hant/calendar.js",
"./locale/zh-hant/clock.js": "./lib/locale/zh-hant/clock.js",
"./locale/zh-hant/color-dialog.js": "./lib/locale/zh-hant/color-dialog.js",
+ "./locale/zh-hant/color-picker.js": "./lib/locale/zh-hant/color-picker.js",
"./locale/zh-hant/combo-box.js": "./lib/locale/zh-hant/combo-box.js",
"./locale/zh-hant/datetime-field.js": "./lib/locale/zh-hant/datetime-field.js",
"./locale/zh-hant/dialog.js": "./lib/locale/zh-hant/dialog.js",
diff --git a/packages/phrasebook/src/locale/de/color-picker.ts b/packages/phrasebook/src/locale/de/color-picker.ts
new file mode 100644
index 0000000000..f198f8713f
--- /dev/null
+++ b/packages/phrasebook/src/locale/de/color-picker.ts
@@ -0,0 +1,11 @@
+// Component docs https://elf.int.refinitiv.com/elements/color-picker.html
+import { Phrasebook } from '../../translation.js';
+import './shared.js';
+import './color-dialog.js';
+
+const translations = {
+};
+
+Phrasebook.define('de', 'ef-color-picker', translations);
+
+export default translations;
diff --git a/packages/phrasebook/src/locale/en/color-picker.ts b/packages/phrasebook/src/locale/en/color-picker.ts
new file mode 100644
index 0000000000..d11ccc5ff9
--- /dev/null
+++ b/packages/phrasebook/src/locale/en/color-picker.ts
@@ -0,0 +1,11 @@
+// Component docs https://elf.int.refinitiv.com/elements/color-picker.html
+import { Phrasebook } from '../../translation.js';
+import './shared.js';
+import './color-dialog.js';
+
+const translations = {
+};
+
+Phrasebook.define('en', 'ef-color-picker', translations);
+
+export default translations;
diff --git a/packages/phrasebook/src/locale/ja/color-picker.ts b/packages/phrasebook/src/locale/ja/color-picker.ts
new file mode 100644
index 0000000000..50e901cd91
--- /dev/null
+++ b/packages/phrasebook/src/locale/ja/color-picker.ts
@@ -0,0 +1,11 @@
+// Component docs https://elf.int.refinitiv.com/elements/color-picker.html
+import { Phrasebook } from '../../translation.js';
+import './shared.js';
+import './color-dialog.js';
+
+const translations = {
+};
+
+Phrasebook.define('ja', 'ef-color-picker', translations);
+
+export default translations;
diff --git a/packages/phrasebook/src/locale/zh-hant/color-picker.ts b/packages/phrasebook/src/locale/zh-hant/color-picker.ts
new file mode 100644
index 0000000000..7f6ab4db4f
--- /dev/null
+++ b/packages/phrasebook/src/locale/zh-hant/color-picker.ts
@@ -0,0 +1,11 @@
+// Component docs https://elf.int.refinitiv.com/elements/color-picker.html
+import { Phrasebook } from '../../translation.js';
+import './shared.js';
+import './color-dialog.js';
+
+const translations = {
+};
+
+Phrasebook.define('zh-hant', 'ef-color-picker', translations);
+
+export default translations;
diff --git a/packages/phrasebook/src/locale/zh/color-picker.ts b/packages/phrasebook/src/locale/zh/color-picker.ts
new file mode 100644
index 0000000000..3ec5bb3135
--- /dev/null
+++ b/packages/phrasebook/src/locale/zh/color-picker.ts
@@ -0,0 +1,11 @@
+// Component docs https://elf.int.refinitiv.com/elements/color-picker.html
+import { Phrasebook } from '../../translation.js';
+import './shared.js';
+import './color-dialog.js';
+
+const translations = {
+};
+
+Phrasebook.define('zh', 'ef-color-picker', translations);
+
+export default translations;
diff --git a/packages/solar-theme/src/custom-elements/ef-color-picker.less b/packages/solar-theme/src/custom-elements/ef-color-picker.less
new file mode 100644
index 0000000000..93e89bb015
--- /dev/null
+++ b/packages/solar-theme/src/custom-elements/ef-color-picker.less
@@ -0,0 +1,7 @@
+@import '@refinitiv-ui/elemental-theme/src/custom-elements/ef-color-picker';
+
+:host {
+ &[focused=visible] {
+ border-style: dotted;
+ }
+}
\ No newline at end of file
diff --git a/packages/utils/src/color.ts b/packages/utils/src/color.ts
index 515db86d33..98403a6944 100644
--- a/packages/utils/src/color.ts
+++ b/packages/utils/src/color.ts
@@ -1 +1,2 @@
export * from './color/d3-color.js';
+export { isHex, removeHashSign } from './color/color.js';
diff --git a/packages/utils/src/color/color.ts b/packages/utils/src/color/color.ts
new file mode 100644
index 0000000000..bc18eeda1a
--- /dev/null
+++ b/packages/utils/src/color/color.ts
@@ -0,0 +1,16 @@
+const HEX_REGEXP = /^#([0-9A-F]{3}){1,2}$/i; // used to validate HEX
+export const isHex = (value: string): boolean => HEX_REGEXP.test(value);
+
+/**
+ * Remove hash (#) sign from hex value
+ * @param hex Hex to check
+ * @returns hex value without # sign
+ */
+export const removeHashSign = (hex: string): string => {
+ if (hex) {
+ if (hex.startsWith('#')) {
+ hex = hex.slice(1);
+ }
+ }
+ return hex;
+};