diff --git a/frontend/package.json b/frontend/package.json
index 7154d5625..f59bf03e9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -38,6 +38,7 @@
"amplitude-js": "^8.21.9",
"angular-password-strength-meter": "npm:@eresearchqut/angular-password-strength-meter@^13.0.7",
"angulartics2": "^14.1.0",
+ "color-string": "^2.0.1",
"convert": "^5.12.0",
"date-fns": "^4.1.0",
"ipaddr.js": "^2.2.0",
diff --git a/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts
index 55af1b0b0..d3f03ff8f 100644
--- a/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts
+++ b/frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts
@@ -193,8 +193,19 @@ export class DbTableWidgetsComponent implements OnInit {
"allow_negative": true
}
`,
- Color: `// No settings required
-// You can use this field to display colors in hex format, like #FF5733 or #333.`,
+ Color: `// Optional: Specify output format for color values
+// Supported formats: "hex", "hex_hash" (default), "rgb", "hsl"
+// Example configuration:
+
+{
+ "format": "hex_hash" // Will display colors as "#FF5733"
+}
+
+// Format options:
+// - "hex": Display as "FF5733" (no hash)
+// - "hex_hash": Display as "#FF5733" (default)
+// - "rgb": Display as "rgb(255, 87, 51)"
+// - "hsl": Display as "hsl(9, 100%, 60%)"`,
}
constructor(
diff --git a/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.html b/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.html
index 99e173663..e97f8054e 100644
--- a/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.html
+++ b/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.html
@@ -5,12 +5,11 @@
[required]="required" [disabled]="disabled" [readonly]="readonly"
attr.data-testid="record-{{label}}-color"
[(ngModel)]="value" (ngModelChange)="onTextInputChange()"
- placeholder="#000000"
- pattern="^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$">
+ placeholder="e.g. #000000, rgb(0,0,0), hsl(0,0%,0%)">
diff --git a/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.ts b/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.ts
index 16d8c5b66..ce38d366e 100644
--- a/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.ts
+++ b/frontend/src/app/components/ui-components/record-edit-fields/color/color.component.ts
@@ -4,6 +4,7 @@ import { BaseEditFieldComponent } from '../base-row-field/base-row-field.compone
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
+import colorString from 'color-string';
@Injectable()
@@ -20,13 +21,105 @@ export class ColorEditComponent extends BaseEditFieldComponent {
get isValidColor(): boolean {
if (!this.value) return false;
- const colorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
- return colorRegex.test(this.value);
+ return this.parseColor(this.value) !== null;
+ }
+
+ get normalizedColorForPicker(): string {
+ const parsed = this.parseColor(this.value);
+ if (parsed) {
+ const [r, g, b] = parsed.value;
+ return `#${this.toHex(r)}${this.toHex(g)}${this.toHex(b)}`;
+ }
+ return '#000000';
+ }
+
+ get formattedColorValue(): string {
+ const parsed = this.parseColor(this.value);
+ if (!parsed) return this.value;
+
+ const format = this.widgetStructure?.widget_params?.format || 'hex_hash';
+ const [r, g, b, a] = parsed.value;
+
+ switch (format) {
+ case 'hex':
+ return colorString.to.hex(r, g, b, a).slice(1); // Remove # prefix
+ case 'hex_hash':
+ return colorString.to.hex(r, g, b, a);
+ case 'rgb':
+ return colorString.to.rgb(r, g, b, a);
+ case 'hsl':
+ // Convert RGB to HSL using built-in conversion
+ const hex = colorString.to.hex(r, g, b, a);
+ const hslParsed = colorString.get.hsl(hex);
+ if (hslParsed) {
+ const [h, s, l, alpha] = hslParsed;
+ return colorString.to.hsl(h, s, l, alpha);
+ }
+ return hex;
+ default:
+ return colorString.to.hex(r, g, b, a);
+ }
+ }
+
+ private parseColor(color: string): any {
+ if (!color) return null;
+
+ // Try parsing with color-string
+ const parsed = colorString.get(color);
+ if (parsed) return parsed;
+
+ // Try hex without hash
+ if (/^[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{3}$/.test(color)) {
+ return colorString.get('#' + color);
+ }
+
+ return null;
+ }
+
+ private toHex(n: number): string {
+ const hex = n.toString(16);
+ return hex.length === 1 ? '0' + hex : hex;
}
onColorPickerChange(event: Event) {
const target = event.target as HTMLInputElement;
- this.value = target.value;
+ const pickerValue = target.value;
+
+ // Convert picker value to desired format
+ const parsed = this.parseColor(pickerValue);
+ if (parsed) {
+ const format = this.widgetStructure?.widget_params?.format || 'hex_hash';
+
+ const [r, g, b, a] = parsed.value;
+
+ switch (format) {
+ case 'hex':
+ this.value = colorString.to.hex(r, g, b, a).slice(1);
+ break;
+ case 'hex_hash':
+ this.value = colorString.to.hex(r, g, b, a);
+ break;
+ case 'rgb':
+ this.value = colorString.to.rgb(r, g, b, a);
+ break;
+ case 'hsl':
+ // Convert RGB to HSL using built-in conversion
+ const hex = colorString.to.hex(r, g, b, a);
+ const hslParsed = colorString.get.hsl(hex);
+ if (hslParsed) {
+ const [h, s, l, alpha] = hslParsed;
+ this.value = colorString.to.hsl(h, s, l, alpha);
+ } else {
+ this.value = hex;
+ }
+ break;
+ default:
+ this.value = colorString.to.hex(r, g, b, a);
+ }
+ } else {
+ this.value = pickerValue;
+ }
+
this.onFieldChange.emit(this.value);
}
diff --git a/frontend/src/app/components/ui-components/table-display-fields/color/color.component.html b/frontend/src/app/components/ui-components/table-display-fields/color/color.component.html
index 331411389..f144cacdc 100644
--- a/frontend/src/app/components/ui-components/table-display-fields/color/color.component.html
+++ b/frontend/src/app/components/ui-components/table-display-fields/color/color.component.html
@@ -2,7 +2,7 @@
{{value || '—'}}
diff --git a/frontend/src/app/components/ui-components/table-display-fields/color/color.component.ts b/frontend/src/app/components/ui-components/table-display-fields/color/color.component.ts
index ed3651077..87755852c 100644
--- a/frontend/src/app/components/ui-components/table-display-fields/color/color.component.ts
+++ b/frontend/src/app/components/ui-components/table-display-fields/color/color.component.ts
@@ -6,6 +6,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { NgIf } from '@angular/common';
+import colorString from 'color-string';
@Injectable()
@Component({
@@ -17,7 +18,36 @@ import { NgIf } from '@angular/common';
export class ColorDisplayComponent extends BaseTableDisplayFieldComponent {
get isValidColor(): boolean {
if (!this.value) return false;
- const colorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
- return colorRegex.test(this.value);
+ return this.parseColor(this.value) !== null;
+ }
+
+ get normalizedColorForDisplay(): string {
+ const parsed = this.parseColor(this.value);
+ if (parsed) {
+ const [r, g, b] = parsed.value;
+ return `#${this.toHex(r)}${this.toHex(g)}${this.toHex(b)}`;
+ }
+ return '#000000';
+ }
+
+
+ private parseColor(color: string): any {
+ if (!color) return null;
+
+ // Try parsing with color-string
+ const parsed = colorString.get(color);
+ if (parsed) return parsed;
+
+ // Try hex without hash
+ if (/^[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{3}$/.test(color)) {
+ return colorString.get('#' + color);
+ }
+
+ return null;
+ }
+
+ private toHex(n: number): string {
+ const hex = n.toString(16);
+ return hex.length === 1 ? '0' + hex : hex;
}
}
\ No newline at end of file
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 110921d91..848bdba2e 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -5691,6 +5691,13 @@ __metadata:
languageName: node
linkType: hard
+"color-name@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "color-name@npm:2.0.0"
+ checksum: 10a1addae41de2987d6b90dbd3cfade266c2e6f680ce21749911df4493b4fae07654862c6b5358bdd13e155461acb4eedaa5e0ba172bf13542cdcca10866cf2b
+ languageName: node
+ linkType: hard
+
"color-name@npm:~1.1.4":
version: 1.1.4
resolution: "color-name@npm:1.1.4"
@@ -5698,6 +5705,15 @@ __metadata:
languageName: node
linkType: hard
+"color-string@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "color-string@npm:2.0.1"
+ dependencies:
+ color-name: ^2.0.0
+ checksum: a5ab024f78f67c0d5c1c995943ff95dce193beaa981492f6a36c05a9939a9db519e2d821d91df15677a47107fe90e4b12fd345729d755c65b543924db05c3a3f
+ languageName: node
+ linkType: hard
+
"colorette@npm:^2.0.10, colorette@npm:^2.0.20":
version: 2.0.20
resolution: "colorette@npm:2.0.20"
@@ -6655,6 +6671,7 @@ __metadata:
amplitude-js: ^8.21.9
angular-password-strength-meter: "npm:@eresearchqut/angular-password-strength-meter@^13.0.7"
angulartics2: ^14.1.0
+ color-string: ^2.0.1
convert: ^5.12.0
date-fns: ^4.1.0
ipaddr.js: ^2.2.0