Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions menu/satus.css
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,43 @@ As our Syntax markup isnt read for <textarea>, is it?
background: #fff;
box-shadow: 0 0 4px rgb(0, 0, 0, .64);
}

.satus-color-picker__hex-container {
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
padding: 0 4px;
}

.satus-color-picker__hex-label {
font-size: 12px;
font-weight: 500;
color: var(--satus-theme-text-color, #606060);
}

.satus-color-picker__hex-input {
flex: 1;
padding: 8px 12px;
font-family: monospace;
font-size: 14px;
text-transform: uppercase;
border: 1px solid var(--satus-theme-border-color, #e0e0e0);
border-radius: 4px;
background: var(--satus-theme-background-color, #fff);
color: var(--satus-theme-text-color, #333);
outline: none;
transition: border-color 0.2s;
}

.satus-color-picker__hex-input:focus {
border-color: var(--satus-theme-primary-color, #065fd4);
}

.satus-color-picker__hex-input::placeholder {
color: var(--satus-theme-secondary-text-color, #909090);
text-transform: none;
}
/*--------------------------------------------------------------
>>> SPAN
--------------------------------------------------------------*/
Expand Down
119 changes: 119 additions & 0 deletions menu/satus.js
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,13 @@ satus.components.colorPicker = function (component, skeleton) {

palette.nextSibling.children[0].style.backgroundColor = 'hsl(' + hsl[0] + 'deg,' + hsl[1] + '%, ' + hsl[2] + '%)';

// Update hex input
var hexInput = palette.parentNode.querySelector('.satus-color-picker__hex-input');
if (hexInput) {
var rgb = satus.color.hslToRgb(hsl);
hexInput.value = satus.color.rgbToHex(rgb);
}

event.preventDefault();
}

Expand Down Expand Up @@ -1900,6 +1907,80 @@ satus.components.colorPicker = function (component, skeleton) {

this.previousSibling.style.backgroundColor = 'hsl(' + hsl[0] + 'deg,' + hsl[1] + '%, ' + hsl[2] + '%)';
this.parentNode.previousSibling.style.backgroundColor = 'hsl(' + hsl[0] + 'deg, 100%, 50%)';

// Update hex input
var hexInput = this.parentNode.querySelector('.satus-color-picker__hex-input');
if (hexInput) {
var rgb = satus.color.hslToRgb(hsl);
hexInput.value = satus.color.rgbToHex(rgb);
}
}
}
},
hexInput: {
component: 'div',
class: 'satus-color-picker__hex-container',

label: {
component: 'span',
class: 'satus-color-picker__hex-label',
text: 'HEX:'
},
input: {
component: 'input',
class: 'satus-color-picker__hex-input',
attr: {
type: 'text',
maxlength: '7',
placeholder: '#000000',
value: satus.color.rgbToHex(component.color.value)
},
on: {
input: function () {
var value = this.value.trim();
var rgb = satus.color.hexToRgb(value);

if (rgb) {
var modal = this.skeleton.parentSkeleton.parentSkeleton.parentSkeleton,
hsl = satus.color.rgbToHsl(rgb);

modal.value = hsl;

// Update color preview
var colorPreview = this.parentNode.parentNode.querySelector('.satus-color-picker__color');
if (colorPreview) {
colorPreview.style.backgroundColor = 'rgb(' + rgb.join(',') + ')';
}

// Update palette background
var palette = this.parentNode.parentNode.previousSibling;
if (palette) {
palette.style.backgroundColor = 'hsl(' + hsl[0] + 'deg, 100%, 50%)';
}

// Update hue slider
var hueSlider = this.parentNode.parentNode.querySelector('.satus-color-picker__hue');
if (hueSlider && hueSlider.querySelector) {
var sliderInput = hueSlider.querySelector('input');
if (sliderInput) {
sliderInput.value = hsl[0];
}
}

// Update cursor position
var s = hsl[1] / 100,
l = hsl[2] / 100;
s *= l < .5 ? l : 1 - l;
var v = l + s;
s = 2 * s / (l + s);

var cursor = palette.querySelector('.satus-color-picker__cursor');
if (cursor) {
cursor.style.left = s * 100 + '%';
cursor.style.top = 100 - v * 100 + '%';
}
}
}
}
}
}
Expand Down Expand Up @@ -2796,6 +2877,44 @@ satus.color.hslToRgb = function (array) {

return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
};

/*--------------------------------------------------------------
# RGB TO HEX
--------------------------------------------------------------*/

satus.color.rgbToHex = function (array) {
var r = Math.round(array[0]).toString(16).padStart(2, '0'),
g = Math.round(array[1]).toString(16).padStart(2, '0'),
b = Math.round(array[2]).toString(16).padStart(2, '0');

return '#' + r + g + b;
};

/*--------------------------------------------------------------
# HEX TO RGB
--------------------------------------------------------------*/

satus.color.hexToRgb = function (hex) {
// Remove # if present
hex = hex.replace(/^#/, '');

// Handle shorthand hex (e.g., #FFF)
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}

// Parse hex values
var r = parseInt(hex.substring(0, 2), 16),
g = parseInt(hex.substring(2, 4), 16),
b = parseInt(hex.substring(4, 6), 16);

// Return null if invalid
if (isNaN(r) || isNaN(g) || isNaN(b)) {
return null;
}

return [r, g, b];
};
/*--------------------------------------------------------------
>>> USER
----------------------------------------------------------------
Expand Down
110 changes: 110 additions & 0 deletions tests/unit/hex-color-input.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Test for Issue #3381: Hexadecimal color input support

const fs = require('fs');
const path = require('path');

describe('Hexadecimal Color Input Feature', () => {
describe('Color Conversion Functions', () => {
let satusContent;

beforeAll(() => {
const satusPath = path.join(__dirname, '../../menu/satus.js');
satusContent = fs.readFileSync(satusPath, 'utf8');
});

test('satus.color.rgbToHex function should exist', () => {
expect(satusContent).toContain('satus.color.rgbToHex');
});

test('satus.color.hexToRgb function should exist', () => {
expect(satusContent).toContain('satus.color.hexToRgb');
});

test('rgbToHex should use padStart for proper formatting', () => {
expect(satusContent).toContain("padStart(2, '0')");
});

test('hexToRgb should handle shorthand hex notation', () => {
expect(satusContent).toContain('hex.length === 3');
});
});

describe('Color Picker UI', () => {
let satusContent;

beforeAll(() => {
const satusPath = path.join(__dirname, '../../menu/satus.js');
satusContent = fs.readFileSync(satusPath, 'utf8');
});

test('color picker should have hex input container', () => {
expect(satusContent).toContain('satus-color-picker__hex-container');
});

test('color picker should have hex input field', () => {
expect(satusContent).toContain('satus-color-picker__hex-input');
});

test('hex input should have maxlength of 7', () => {
expect(satusContent).toContain("maxlength: '7'");
});

test('hex input should have placeholder', () => {
expect(satusContent).toContain("placeholder: '#000000'");
});
});

describe('CSS Styles', () => {
let cssContent;

beforeAll(() => {
const cssPath = path.join(__dirname, '../../menu/satus.css');
cssContent = fs.readFileSync(cssPath, 'utf8');
});

test('hex container styles should exist', () => {
expect(cssContent).toContain('.satus-color-picker__hex-container');
});

test('hex input styles should exist', () => {
expect(cssContent).toContain('.satus-color-picker__hex-input');
});

test('hex label styles should exist', () => {
expect(cssContent).toContain('.satus-color-picker__hex-label');
});

test('hex input should use monospace font', () => {
expect(cssContent).toContain('font-family: monospace');
});
});
});

describe('Color Conversion Logic Tests', () => {
// Simple unit tests for the conversion logic
test('RGB to Hex conversion formula', () => {
// Test the padStart formatting
const r = 255, g = 128, b = 0;
const hex = '#' +
r.toString(16).padStart(2, '0') +
g.toString(16).padStart(2, '0') +
b.toString(16).padStart(2, '0');
expect(hex).toBe('#ff8000');
});

test('Hex to RGB conversion formula', () => {
const hex = 'ff8000';
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
expect([r, g, b]).toEqual([255, 128, 0]);
});

test('Shorthand hex expansion', () => {
let hex = 'fff';
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
expect(hex).toBe('ffffff');
});
});
Loading