triiiceratops 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index-bundle.d.ts +2 -0
- package/dist/index-bundle.js +2 -0
- package/dist/plugins/image-manipulation/ImageManipulationPlugin.svelte.js +37 -72
- package/dist/plugins/image-manipulation/filters.js +6 -8
- package/dist/plugins/image-manipulation/types.js +1 -1
- package/dist/state/i18n.svelte.d.ts +3 -2
- package/dist/state/i18n.svelte.js +5 -5
- package/dist/state/manifests.svelte.js +77 -149
- package/dist/state/manifests.test.js +128 -223
- package/dist/state/viewer.svelte.d.ts +2 -2
- package/dist/state/viewer.svelte.js +374 -513
- package/dist/theme/colorUtils.js +35 -35
- package/dist/theme/colorUtils.test.js +30 -30
- package/dist/theme/themeManager.js +10 -12
- package/dist/theme/types.js +1 -1
- package/dist/triiiceratops.css +1 -0
- package/dist/types/plugin.js +18 -31
- package/dist/utils/annotationAdapter.js +80 -93
- package/dist/utils/annotationAdapter.test.js +24 -24
- package/package.json +3 -3
package/dist/theme/colorUtils.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
function parseHex(hex) {
|
|
10
10
|
// Remove # prefix if present
|
|
11
11
|
hex = hex.replace(/^#/, '');
|
|
12
|
-
|
|
12
|
+
let r, g, b;
|
|
13
13
|
if (hex.length === 3) {
|
|
14
14
|
// Short form #rgb
|
|
15
15
|
r = parseInt(hex[0] + hex[0], 16);
|
|
@@ -28,35 +28,35 @@ function parseHex(hex) {
|
|
|
28
28
|
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
|
-
return { r
|
|
31
|
+
return { r, g, b };
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Parse an rgb() or rgba() color string to RGB values (0-255)
|
|
35
35
|
*/
|
|
36
36
|
function parseRgb(rgb) {
|
|
37
37
|
// Match rgb(r, g, b) or rgba(r, g, b, a) or modern rgb(r g b) / rgb(r g b / a)
|
|
38
|
-
|
|
38
|
+
const match = rgb.match(/rgba?\(\s*(\d+(?:\.\d+)?%?)\s*[,\s]\s*(\d+(?:\.\d+)?%?)\s*[,\s]\s*(\d+(?:\.\d+)?%?)(?:\s*[,/]\s*(\d*\.?\d+%?))?\s*\)/i);
|
|
39
39
|
if (!match)
|
|
40
40
|
return null;
|
|
41
|
-
|
|
41
|
+
const parseValue = (val) => {
|
|
42
42
|
if (val.endsWith('%')) {
|
|
43
43
|
return (parseFloat(val) / 100) * 255;
|
|
44
44
|
}
|
|
45
45
|
return parseFloat(val);
|
|
46
46
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const r = parseValue(match[1]);
|
|
48
|
+
const g = parseValue(match[2]);
|
|
49
|
+
const b = parseValue(match[3]);
|
|
50
50
|
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
|
-
return { r
|
|
53
|
+
return { r, g, b };
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Convert sRGB to linear RGB
|
|
57
57
|
*/
|
|
58
58
|
function srgbToLinear(c) {
|
|
59
|
-
|
|
59
|
+
const v = c / 255;
|
|
60
60
|
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
@@ -75,13 +75,13 @@ function linearRgbToXyz(r, g, b) {
|
|
|
75
75
|
*/
|
|
76
76
|
function xyzToOklab(x, y, z) {
|
|
77
77
|
// XYZ to LMS (using OKLAB's M1 matrix)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const l = 0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z;
|
|
79
|
+
const m = 0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z;
|
|
80
|
+
const s = 0.0482003018 * x + 0.2643662691 * y + 0.633851707 * z;
|
|
81
81
|
// Cube root
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const l_ = Math.cbrt(l);
|
|
83
|
+
const m_ = Math.cbrt(m);
|
|
84
|
+
const s_ = Math.cbrt(s);
|
|
85
85
|
// LMS to OKLAB (using OKLAB's M2 matrix)
|
|
86
86
|
return {
|
|
87
87
|
L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
@@ -93,24 +93,24 @@ function xyzToOklab(x, y, z) {
|
|
|
93
93
|
* Convert OKLAB to OKLCH
|
|
94
94
|
*/
|
|
95
95
|
function oklabToOklch(L, a, b) {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const C = Math.sqrt(a * a + b * b);
|
|
97
|
+
let h = (Math.atan2(b, a) * 180) / Math.PI;
|
|
98
98
|
if (h < 0)
|
|
99
99
|
h += 360;
|
|
100
|
-
return { L
|
|
100
|
+
return { L, C, h };
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* Convert RGB (0-255) to OKLCH
|
|
104
104
|
*/
|
|
105
105
|
function rgbToOklch(r, g, b) {
|
|
106
106
|
// sRGB to linear RGB
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
const lr = srgbToLinear(r);
|
|
108
|
+
const lg = srgbToLinear(g);
|
|
109
|
+
const lb = srgbToLinear(b);
|
|
110
110
|
// Linear RGB to XYZ
|
|
111
|
-
|
|
111
|
+
const { x, y, z } = linearRgbToXyz(lr, lg, lb);
|
|
112
112
|
// XYZ to OKLAB
|
|
113
|
-
|
|
113
|
+
const { L, a, b: labB } = xyzToOklab(x, y, z);
|
|
114
114
|
// OKLAB to OKLCH
|
|
115
115
|
return oklabToOklch(L, a, labB);
|
|
116
116
|
}
|
|
@@ -122,10 +122,10 @@ function rgbToOklch(r, g, b) {
|
|
|
122
122
|
*/
|
|
123
123
|
function formatOklch(L, C, h) {
|
|
124
124
|
// L as percentage, C as decimal, h in degrees
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return
|
|
125
|
+
const lPercent = (L * 100).toFixed(2);
|
|
126
|
+
const cValue = C.toFixed(4);
|
|
127
|
+
const hValue = C < 0.0001 ? 0 : h.toFixed(2); // For achromatic colors, hue is meaningless
|
|
128
|
+
return `oklch(${lPercent}% ${cValue} ${hValue})`;
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* Check if a string is already in oklch format
|
|
@@ -151,12 +151,12 @@ export function isRgb(value) {
|
|
|
151
151
|
* @returns oklch string like "oklch(65.00% 0.2000 250.00)"
|
|
152
152
|
*/
|
|
153
153
|
export function hexToOklch(hex) {
|
|
154
|
-
|
|
154
|
+
const rgb = parseHex(hex);
|
|
155
155
|
if (!rgb) {
|
|
156
|
-
console.warn(
|
|
156
|
+
console.warn(`Invalid hex color: ${hex}, returning as-is`);
|
|
157
157
|
return hex;
|
|
158
158
|
}
|
|
159
|
-
|
|
159
|
+
const { L, C, h } = rgbToOklch(rgb.r, rgb.g, rgb.b);
|
|
160
160
|
return formatOklch(L, C, h);
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
@@ -165,12 +165,12 @@ export function hexToOklch(hex) {
|
|
|
165
165
|
* @returns oklch string like "oklch(65.00% 0.2000 250.00)"
|
|
166
166
|
*/
|
|
167
167
|
export function rgbToOklchString(rgb) {
|
|
168
|
-
|
|
168
|
+
const parsed = parseRgb(rgb);
|
|
169
169
|
if (!parsed) {
|
|
170
|
-
console.warn(
|
|
170
|
+
console.warn(`Invalid rgb color: ${rgb}, returning as-is`);
|
|
171
171
|
return rgb;
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
const { L, C, h } = rgbToOklch(parsed.r, parsed.g, parsed.b);
|
|
174
174
|
return formatOklch(L, C, h);
|
|
175
175
|
}
|
|
176
176
|
/**
|
|
@@ -180,7 +180,7 @@ export function rgbToOklchString(rgb) {
|
|
|
180
180
|
* @returns oklch string or original value if already oklch
|
|
181
181
|
*/
|
|
182
182
|
export function normalizeColor(color) {
|
|
183
|
-
|
|
183
|
+
const trimmed = color.trim();
|
|
184
184
|
if (isOklch(trimmed)) {
|
|
185
185
|
return trimmed;
|
|
186
186
|
}
|
|
@@ -191,6 +191,6 @@ export function normalizeColor(color) {
|
|
|
191
191
|
return rgbToOklchString(trimmed);
|
|
192
192
|
}
|
|
193
193
|
// Pass through as-is (could be a CSS variable, named color, etc.)
|
|
194
|
-
console.warn(
|
|
194
|
+
console.warn(`Unrecognized color format: ${color}, passing through as-is`);
|
|
195
195
|
return color;
|
|
196
196
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { hexToOklch, normalizeColor, isOklch, isHex, isRgb, } from './colorUtils';
|
|
3
|
-
describe('colorUtils',
|
|
4
|
-
describe('isHex',
|
|
5
|
-
it('should return true for valid hex colors',
|
|
3
|
+
describe('colorUtils', () => {
|
|
4
|
+
describe('isHex', () => {
|
|
5
|
+
it('should return true for valid hex colors', () => {
|
|
6
6
|
expect(isHex('#fff')).toBe(true);
|
|
7
7
|
expect(isHex('#FFF')).toBe(true);
|
|
8
8
|
expect(isHex('#ffffff')).toBe(true);
|
|
@@ -11,7 +11,7 @@ describe('colorUtils', function () {
|
|
|
11
11
|
expect(isHex('#3B82F6')).toBe(true);
|
|
12
12
|
expect(isHex('#3b82f6ff')).toBe(true); // with alpha
|
|
13
13
|
});
|
|
14
|
-
it('should return false for invalid hex colors',
|
|
14
|
+
it('should return false for invalid hex colors', () => {
|
|
15
15
|
expect(isHex('fff')).toBe(false);
|
|
16
16
|
expect(isHex('#ff')).toBe(false);
|
|
17
17
|
expect(isHex('#ffff')).toBe(false);
|
|
@@ -20,70 +20,70 @@ describe('colorUtils', function () {
|
|
|
20
20
|
expect(isHex('oklch(50% 0.2 250)')).toBe(false);
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
|
-
describe('isRgb',
|
|
24
|
-
it('should return true for valid rgb colors',
|
|
23
|
+
describe('isRgb', () => {
|
|
24
|
+
it('should return true for valid rgb colors', () => {
|
|
25
25
|
expect(isRgb('rgb(255, 255, 255)')).toBe(true);
|
|
26
26
|
expect(isRgb('rgb(0, 0, 0)')).toBe(true);
|
|
27
27
|
expect(isRgb('rgba(255, 255, 255, 0.5)')).toBe(true);
|
|
28
28
|
expect(isRgb('rgb(59 130 246)')).toBe(true); // modern syntax
|
|
29
29
|
});
|
|
30
|
-
it('should return false for non-rgb colors',
|
|
30
|
+
it('should return false for non-rgb colors', () => {
|
|
31
31
|
expect(isRgb('#fff')).toBe(false);
|
|
32
32
|
expect(isRgb('oklch(50% 0.2 250)')).toBe(false);
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
|
-
describe('isOklch',
|
|
36
|
-
it('should return true for oklch colors',
|
|
35
|
+
describe('isOklch', () => {
|
|
36
|
+
it('should return true for oklch colors', () => {
|
|
37
37
|
expect(isOklch('oklch(50% 0.2 250)')).toBe(true);
|
|
38
38
|
expect(isOklch('OKLCH(50% 0.2 250)')).toBe(true);
|
|
39
39
|
expect(isOklch(' oklch(50% 0.2 250) ')).toBe(true);
|
|
40
40
|
});
|
|
41
|
-
it('should return false for non-oklch colors',
|
|
41
|
+
it('should return false for non-oklch colors', () => {
|
|
42
42
|
expect(isOklch('#fff')).toBe(false);
|
|
43
43
|
expect(isOklch('rgb(255, 255, 255)')).toBe(false);
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
|
-
describe('hexToOklch',
|
|
47
|
-
it('should convert black correctly',
|
|
48
|
-
|
|
46
|
+
describe('hexToOklch', () => {
|
|
47
|
+
it('should convert black correctly', () => {
|
|
48
|
+
const result = hexToOklch('#000000');
|
|
49
49
|
expect(result).toMatch(/^oklch\(0\.00%/);
|
|
50
50
|
});
|
|
51
|
-
it('should convert white correctly',
|
|
52
|
-
|
|
51
|
+
it('should convert white correctly', () => {
|
|
52
|
+
const result = hexToOklch('#ffffff');
|
|
53
53
|
// White should have high lightness and low chroma
|
|
54
54
|
expect(result).toMatch(/^oklch\(100\.00%/);
|
|
55
55
|
});
|
|
56
|
-
it('should convert primary blue correctly',
|
|
57
|
-
|
|
56
|
+
it('should convert primary blue correctly', () => {
|
|
57
|
+
const result = hexToOklch('#3b82f6');
|
|
58
58
|
// Should produce a valid oklch string
|
|
59
59
|
expect(result).toMatch(/^oklch\(\d+\.\d+% \d+\.\d+ \d+\.\d+\)$/);
|
|
60
60
|
});
|
|
61
|
-
it('should handle short hex notation',
|
|
62
|
-
|
|
61
|
+
it('should handle short hex notation', () => {
|
|
62
|
+
const result = hexToOklch('#fff');
|
|
63
63
|
expect(result).toMatch(/^oklch\(100\.00%/);
|
|
64
64
|
});
|
|
65
|
-
it('should handle hex with alpha (ignoring alpha)',
|
|
66
|
-
|
|
65
|
+
it('should handle hex with alpha (ignoring alpha)', () => {
|
|
66
|
+
const result = hexToOklch('#3b82f680');
|
|
67
67
|
// Should produce same result as without alpha
|
|
68
68
|
expect(result).toMatch(/^oklch\(\d+\.\d+% \d+\.\d+ \d+\.\d+\)$/);
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
|
-
describe('normalizeColor',
|
|
72
|
-
it('should pass through oklch colors unchanged',
|
|
73
|
-
|
|
71
|
+
describe('normalizeColor', () => {
|
|
72
|
+
it('should pass through oklch colors unchanged', () => {
|
|
73
|
+
const oklch = 'oklch(65.00% 0.2000 250.00)';
|
|
74
74
|
expect(normalizeColor(oklch)).toBe(oklch);
|
|
75
75
|
});
|
|
76
|
-
it('should convert hex to oklch',
|
|
77
|
-
|
|
76
|
+
it('should convert hex to oklch', () => {
|
|
77
|
+
const result = normalizeColor('#3b82f6');
|
|
78
78
|
expect(result).toMatch(/^oklch\(/);
|
|
79
79
|
});
|
|
80
|
-
it('should convert rgb to oklch',
|
|
81
|
-
|
|
80
|
+
it('should convert rgb to oklch', () => {
|
|
81
|
+
const result = normalizeColor('rgb(59, 130, 246)');
|
|
82
82
|
expect(result).toMatch(/^oklch\(/);
|
|
83
83
|
});
|
|
84
|
-
it('should pass through unknown formats with warning',
|
|
84
|
+
it('should pass through unknown formats with warning', () => {
|
|
85
85
|
// Named colors, CSS variables, etc. should pass through
|
|
86
|
-
|
|
86
|
+
const result = normalizeColor('var(--some-color)');
|
|
87
87
|
expect(result).toBe('var(--some-color)');
|
|
88
88
|
});
|
|
89
89
|
});
|
|
@@ -6,7 +6,7 @@ import { normalizeColor } from './colorUtils';
|
|
|
6
6
|
/**
|
|
7
7
|
* Map friendly ThemeConfig property names to DaisyUI CSS variable names
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
const CSS_VAR_MAP = {
|
|
10
10
|
// Semantic colors
|
|
11
11
|
primary: '--color-primary',
|
|
12
12
|
primaryContent: '--color-primary-content',
|
|
@@ -48,7 +48,7 @@ var CSS_VAR_MAP = {
|
|
|
48
48
|
/**
|
|
49
49
|
* Set of properties that are colors and need oklch conversion
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
const COLOR_PROPS = new Set([
|
|
52
52
|
'primary',
|
|
53
53
|
'primaryContent',
|
|
54
54
|
'secondary',
|
|
@@ -87,20 +87,19 @@ export function applyBuiltInTheme(element, theme) {
|
|
|
87
87
|
* These override the base theme's values.
|
|
88
88
|
*/
|
|
89
89
|
export function applyThemeConfig(element, config) {
|
|
90
|
-
for (
|
|
91
|
-
var _b = _a[_i], key = _b[0], value = _b[1];
|
|
90
|
+
for (const [key, value] of Object.entries(config)) {
|
|
92
91
|
if (value === undefined || value === null)
|
|
93
92
|
continue;
|
|
94
|
-
|
|
93
|
+
const propKey = key;
|
|
95
94
|
// Handle colorScheme specially - it's not a CSS variable
|
|
96
95
|
if (propKey === 'colorScheme') {
|
|
97
96
|
element.style.colorScheme = value;
|
|
98
97
|
continue;
|
|
99
98
|
}
|
|
100
|
-
|
|
99
|
+
const cssVar = CSS_VAR_MAP[propKey];
|
|
101
100
|
if (!cssVar)
|
|
102
101
|
continue;
|
|
103
|
-
|
|
102
|
+
let cssValue = String(value);
|
|
104
103
|
// Convert colors to oklch format
|
|
105
104
|
if (COLOR_PROPS.has(propKey)) {
|
|
106
105
|
cssValue = normalizeColor(cssValue);
|
|
@@ -112,8 +111,7 @@ export function applyThemeConfig(element, config) {
|
|
|
112
111
|
* Clear all custom theme CSS variables from an element
|
|
113
112
|
*/
|
|
114
113
|
export function clearThemeConfig(element) {
|
|
115
|
-
for (
|
|
116
|
-
var _b = _a[_i], key = _b[0], cssVar = _b[1];
|
|
114
|
+
for (const [key, cssVar] of Object.entries(CSS_VAR_MAP)) {
|
|
117
115
|
if (key === 'colorScheme') {
|
|
118
116
|
element.style.colorScheme = '';
|
|
119
117
|
}
|
|
@@ -127,13 +125,13 @@ export function clearThemeConfig(element) {
|
|
|
127
125
|
*/
|
|
128
126
|
export function parseThemeConfig(json) {
|
|
129
127
|
try {
|
|
130
|
-
|
|
128
|
+
const parsed = JSON.parse(json);
|
|
131
129
|
if (typeof parsed === 'object' && parsed !== null) {
|
|
132
130
|
return parsed;
|
|
133
131
|
}
|
|
134
132
|
return null;
|
|
135
133
|
}
|
|
136
|
-
catch
|
|
134
|
+
catch {
|
|
137
135
|
return null;
|
|
138
136
|
}
|
|
139
137
|
}
|
|
@@ -167,7 +165,7 @@ export function applyTheme(element, theme, config) {
|
|
|
167
165
|
* Get all CSS variable names that can be customized
|
|
168
166
|
*/
|
|
169
167
|
export function getThemeCssVariables() {
|
|
170
|
-
return Object.values(CSS_VAR_MAP).filter(
|
|
168
|
+
return Object.values(CSS_VAR_MAP).filter((v) => v !== 'color-scheme');
|
|
171
169
|
}
|
|
172
170
|
/**
|
|
173
171
|
* Get all customizable theme property names
|