v-auto-color 1.0.0
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/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/dist/chunk-GO6JHW6J.mjs +72 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +133 -0
- package/dist/index.mjs +45 -0
- package/dist/vite-plugin.d.mts +5 -0
- package/dist/vite-plugin.d.ts +5 -0
- package/dist/vite-plugin.js +158 -0
- package/dist/vite-plugin.mjs +71 -0
- package/examples/VueExample.vue +77 -0
- package/examples/vite.config.example.ts +11 -0
- package/package.json +43 -0
- package/src/core/color.ts +46 -0
- package/src/core/hash.ts +44 -0
- package/src/index.ts +55 -0
- package/src/vite-plugin.ts +91 -0
- package/tsconfig.json +24 -0
- package/tsconfig.node.json +10 -0
package/.gitattributes
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zeki T. Luan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Vue-AutoColor
|
|
2
|
+
|
|
3
|
+
一个兼容 vite/typescript/vue 环境的 node module,用于在 vite 编译时实现基于文本的自动颜色生成。相似的文本会返回相似的颜色,从而实现视觉上的一致性。
|
|
4
|
+
|
|
5
|
+
## 功能特点
|
|
6
|
+
|
|
7
|
+
- **基于文本的颜色生成**:根据纯文本计算颜色,相似的文本返回相似的颜色
|
|
8
|
+
- **编译时优化**:在 vite 编译时预先计算已使用的颜色,降低运行时负载
|
|
9
|
+
- **运行时 fallback**:对于未预编译的颜色,在运行时自动生成并缓存
|
|
10
|
+
- **支持自定义配置**:可以配置颜色的色相范围、饱和度范围和亮度范围
|
|
11
|
+
- **TypeScript 支持**:完整的类型定义,提供良好的开发体验
|
|
12
|
+
- **Vue 兼容**:可以在 Vue 组件中轻松使用
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install v-auto-color
|
|
18
|
+
# 或
|
|
19
|
+
yarn add v-auto-color
|
|
20
|
+
# 或
|
|
21
|
+
pnpm add v-auto-color
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 使用方法
|
|
25
|
+
|
|
26
|
+
### 1. 在 vite.config.ts 中配置插件
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { defineConfig } from 'vite';
|
|
30
|
+
import vue from '@vitejs/plugin-vue';
|
|
31
|
+
import { viteAutoColorPlugin } from 'v-auto-color';
|
|
32
|
+
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
plugins: [
|
|
35
|
+
vue(),
|
|
36
|
+
viteAutoColorPlugin() // 启用 v-auto-color 插件
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. 在代码中使用
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { useAutoColor } from 'v-auto-color';
|
|
45
|
+
|
|
46
|
+
// 使用默认配置
|
|
47
|
+
const colorSet1 = useAutoColor('set1');
|
|
48
|
+
const color1 = colorSet1.getColor('text');
|
|
49
|
+
|
|
50
|
+
// 使用自定义配置
|
|
51
|
+
const colorSet2 = useAutoColor({
|
|
52
|
+
category: 'set2',
|
|
53
|
+
hue: [180, 360], // 蓝色调范围
|
|
54
|
+
saturation: [60, 90],
|
|
55
|
+
lightness: [40, 70]
|
|
56
|
+
});
|
|
57
|
+
const color2 = colorSet2.getColor('text');
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. 在 Vue 组件中使用
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<template>
|
|
64
|
+
<div :style="`--color: ${colorSet1.getColor('text')}`">text</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
import { useAutoColor } from 'v-auto-color';
|
|
69
|
+
|
|
70
|
+
const colorSet1 = useAutoColor('set1');
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 配置选项
|
|
75
|
+
|
|
76
|
+
### useAutoColor 配置
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface ColorConfig {
|
|
80
|
+
category?: string; // 颜色集类别,默认 'default'
|
|
81
|
+
hue?: [number, number]; // 色相范围,默认 [0, 360]
|
|
82
|
+
saturation?: [number, number]; // 饱和度范围,默认 [70, 100]
|
|
83
|
+
lightness?: [number, number]; // 亮度范围,默认 [40, 60]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 两种使用方式
|
|
87
|
+
const colorSet1 = useAutoColor('category'); // 使用字符串作为类别
|
|
88
|
+
const colorSet2 = useAutoColor({ /* 完整配置 */ }); // 使用对象配置
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 性能优化
|
|
92
|
+
|
|
93
|
+
### 编译时预计算
|
|
94
|
+
|
|
95
|
+
插件会在 vite 编译时分析代码,提取所有 `useAutoColor` 和 `getColor` 的调用,预先计算出所有使用的颜色,并在构建时注入到代码中。这样在运行时,大部分颜色都可以直接从缓存中获取,无需重新计算。
|
|
96
|
+
|
|
97
|
+
### 运行时缓存
|
|
98
|
+
|
|
99
|
+
对于未在编译时预计算的颜色(例如动态生成的文本),插件会在运行时生成颜色并缓存起来,避免重复计算。
|
|
100
|
+
|
|
101
|
+
### 哈希算法优化
|
|
102
|
+
|
|
103
|
+
使用 MurmurHash3 算法计算文本哈希值,该算法具有以下特点:
|
|
104
|
+
- 计算速度快
|
|
105
|
+
- 分布均匀
|
|
106
|
+
- 碰撞率低
|
|
107
|
+
|
|
108
|
+
## 工作原理
|
|
109
|
+
|
|
110
|
+
1. **文本哈希**:使用 MurmurHash3 算法对文本进行哈希计算,得到一个数值
|
|
111
|
+
2. **颜色生成**:根据哈希值计算 HSL 颜色,确保相似的文本返回相似的颜色
|
|
112
|
+
3. **编译时分析**:在 vite 编译时分析代码,提取颜色使用情况
|
|
113
|
+
4. **预计算颜色**:为提取的文本预先计算颜色
|
|
114
|
+
5. **运行时使用**:在运行时优先使用预计算的颜色,未预计算的则动态生成
|
|
115
|
+
|
|
116
|
+
## 示例
|
|
117
|
+
|
|
118
|
+
### 基本示例
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { useAutoColor } from 'v-auto-color';
|
|
122
|
+
|
|
123
|
+
const colorSet = useAutoColor('default');
|
|
124
|
+
|
|
125
|
+
// 相似的文本会返回相似的颜色
|
|
126
|
+
const color1 = colorSet.getColor('hello');
|
|
127
|
+
const color2 = colorSet.getColor('hello world');
|
|
128
|
+
const color3 = colorSet.getColor('hello there');
|
|
129
|
+
|
|
130
|
+
console.log(color1); // 例如: hsl(120, 80%, 50%)
|
|
131
|
+
console.log(color2); // 例如: hsl(125, 75%, 45%)
|
|
132
|
+
console.log(color3); // 例如: hsl(115, 85%, 55%)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Vue 组件示例
|
|
136
|
+
|
|
137
|
+
```vue
|
|
138
|
+
<template>
|
|
139
|
+
<div class="tag-container">
|
|
140
|
+
<span
|
|
141
|
+
v-for="tag in tags"
|
|
142
|
+
:key="tag"
|
|
143
|
+
class="tag"
|
|
144
|
+
:style="`--tag-color: ${colorSet.getColor(tag)}`"
|
|
145
|
+
>
|
|
146
|
+
{{ tag }}
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
</template>
|
|
150
|
+
|
|
151
|
+
<script setup lang="ts">
|
|
152
|
+
import { useAutoColor } from 'v-auto-color';
|
|
153
|
+
|
|
154
|
+
const colorSet = useAutoColor('tags');
|
|
155
|
+
const tags = ['JavaScript', 'TypeScript', 'Vue', 'React', 'Node.js'];
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<style scoped>
|
|
159
|
+
.tag {
|
|
160
|
+
display: inline-block;
|
|
161
|
+
padding: 4px 12px;
|
|
162
|
+
margin: 4px;
|
|
163
|
+
background-color: var(--tag-color);
|
|
164
|
+
color: white;
|
|
165
|
+
border-radius: 16px;
|
|
166
|
+
font-size: 14px;
|
|
167
|
+
}
|
|
168
|
+
</style>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 常见问题
|
|
172
|
+
|
|
173
|
+
### 颜色不一致
|
|
174
|
+
|
|
175
|
+
如果发现相似的文本返回的颜色差异较大,可能是因为:
|
|
176
|
+
- 文本差异过大,导致哈希值差异较大
|
|
177
|
+
- 色相范围设置过小,导致颜色变化不明显
|
|
178
|
+
|
|
179
|
+
可以尝试调整 `hue` 配置选项,扩大色相范围。
|
|
180
|
+
|
|
181
|
+
### 性能问题
|
|
182
|
+
|
|
183
|
+
对于大量动态文本的场景,建议:
|
|
184
|
+
- 尽可能在编译时确定文本内容,以便插件能够预计算颜色
|
|
185
|
+
- 对于动态生成的文本,可以考虑缓存结果,避免重复调用 `getColor`
|
|
186
|
+
|
|
187
|
+
### 颜色质量
|
|
188
|
+
|
|
189
|
+
如果生成的颜色质量不佳,可以调整配置选项:
|
|
190
|
+
- `saturation`:调整颜色的饱和度,值越高颜色越鲜艳
|
|
191
|
+
- `lightness`:调整颜色的亮度,值适中时颜色效果最好
|
|
192
|
+
|
|
193
|
+
## 浏览器兼容性
|
|
194
|
+
|
|
195
|
+
本插件生成的是标准的 HSL 颜色值,支持所有现代浏览器。对于 IE11 等旧浏览器,可能需要使用 HEX 颜色值的 polyfill。
|
|
196
|
+
|
|
197
|
+
## 许可证
|
|
198
|
+
|
|
199
|
+
MIT License
|
|
200
|
+
|
|
201
|
+
## 贡献
|
|
202
|
+
|
|
203
|
+
欢迎提交 Issue 和 Pull Request!
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/core/hash.ts
|
|
6
|
+
function murmurHash3(text) {
|
|
7
|
+
let h1 = 3735928559;
|
|
8
|
+
const c1 = 3432918353;
|
|
9
|
+
const c2 = 461845907;
|
|
10
|
+
const r1 = 15;
|
|
11
|
+
const r2 = 13;
|
|
12
|
+
const m = 5;
|
|
13
|
+
const n = 3864292196;
|
|
14
|
+
let i = 0;
|
|
15
|
+
const length = text.length;
|
|
16
|
+
let k1 = 0;
|
|
17
|
+
while (i < length) {
|
|
18
|
+
const char = text.charCodeAt(i++);
|
|
19
|
+
k1 = k1 << 8 | char;
|
|
20
|
+
}
|
|
21
|
+
k1 = k1 * c1 >>> 0;
|
|
22
|
+
k1 = (k1 << r1 | k1 >>> 32 - r1) >>> 0;
|
|
23
|
+
k1 = k1 * c2 >>> 0;
|
|
24
|
+
h1 ^= k1;
|
|
25
|
+
h1 = (h1 << r2 | h1 >>> 32 - r2) >>> 0;
|
|
26
|
+
h1 = h1 * m + n >>> 0;
|
|
27
|
+
h1 ^= length;
|
|
28
|
+
h1 ^= h1 >>> 16;
|
|
29
|
+
h1 = h1 * 2246822507 >>> 0;
|
|
30
|
+
h1 ^= h1 >>> 13;
|
|
31
|
+
h1 = h1 * 3266489909 >>> 0;
|
|
32
|
+
h1 ^= h1 >>> 16;
|
|
33
|
+
return h1;
|
|
34
|
+
}
|
|
35
|
+
function getTextHash(text) {
|
|
36
|
+
return murmurHash3(text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/core/color.ts
|
|
40
|
+
var ColorGenerator = class {
|
|
41
|
+
constructor(config = {}) {
|
|
42
|
+
__publicField(this, "config");
|
|
43
|
+
this.config = {
|
|
44
|
+
category: config.category || "default",
|
|
45
|
+
hue: config.hue || [0, 360],
|
|
46
|
+
saturation: config.saturation || [70, 100],
|
|
47
|
+
lightness: config.lightness || [40, 60]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Generate color from hash value
|
|
51
|
+
generateColor(hash) {
|
|
52
|
+
const { hue, saturation, lightness } = this.config;
|
|
53
|
+
const hueRange = hue[1] - hue[0];
|
|
54
|
+
const calculatedHue = hue[0] + hash % hueRange;
|
|
55
|
+
const satRange = saturation[1] - saturation[0];
|
|
56
|
+
const calculatedSat = saturation[0] + (hash >> 8) % satRange;
|
|
57
|
+
const lightRange = lightness[1] - lightness[0];
|
|
58
|
+
const calculatedLight = lightness[0] + (hash >> 16) % lightRange;
|
|
59
|
+
return `hsl(${calculatedHue}, ${calculatedSat}%, ${calculatedLight}%)`;
|
|
60
|
+
}
|
|
61
|
+
// Get color for text (wrapper method)
|
|
62
|
+
getColor(text, hashFn) {
|
|
63
|
+
const hash = hashFn(text);
|
|
64
|
+
return this.generateColor(hash);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
__publicField,
|
|
70
|
+
getTextHash,
|
|
71
|
+
ColorGenerator
|
|
72
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface ColorConfig {
|
|
2
|
+
category?: string;
|
|
3
|
+
hue?: [number, number];
|
|
4
|
+
saturation?: [number, number];
|
|
5
|
+
lightness?: [number, number];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare class ColorSet {
|
|
9
|
+
private config;
|
|
10
|
+
private generator;
|
|
11
|
+
private category;
|
|
12
|
+
constructor(config?: ColorConfig | string);
|
|
13
|
+
getColor(text: string): string;
|
|
14
|
+
}
|
|
15
|
+
declare function useAutoColor(config?: ColorConfig | string): ColorSet;
|
|
16
|
+
declare function __internal__setPrecomputedColors(colors: Record<string, Record<string, string>>): void;
|
|
17
|
+
|
|
18
|
+
export { type ColorConfig, ColorSet, __internal__setPrecomputedColors, useAutoColor };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface ColorConfig {
|
|
2
|
+
category?: string;
|
|
3
|
+
hue?: [number, number];
|
|
4
|
+
saturation?: [number, number];
|
|
5
|
+
lightness?: [number, number];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare class ColorSet {
|
|
9
|
+
private config;
|
|
10
|
+
private generator;
|
|
11
|
+
private category;
|
|
12
|
+
constructor(config?: ColorConfig | string);
|
|
13
|
+
getColor(text: string): string;
|
|
14
|
+
}
|
|
15
|
+
declare function useAutoColor(config?: ColorConfig | string): ColorSet;
|
|
16
|
+
declare function __internal__setPrecomputedColors(colors: Record<string, Record<string, string>>): void;
|
|
17
|
+
|
|
18
|
+
export { type ColorConfig, ColorSet, __internal__setPrecomputedColors, useAutoColor };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
ColorSet: () => ColorSet,
|
|
26
|
+
__internal__setPrecomputedColors: () => __internal__setPrecomputedColors,
|
|
27
|
+
useAutoColor: () => useAutoColor
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/core/hash.ts
|
|
32
|
+
function murmurHash3(text) {
|
|
33
|
+
let h1 = 3735928559;
|
|
34
|
+
const c1 = 3432918353;
|
|
35
|
+
const c2 = 461845907;
|
|
36
|
+
const r1 = 15;
|
|
37
|
+
const r2 = 13;
|
|
38
|
+
const m = 5;
|
|
39
|
+
const n = 3864292196;
|
|
40
|
+
let i = 0;
|
|
41
|
+
const length = text.length;
|
|
42
|
+
let k1 = 0;
|
|
43
|
+
while (i < length) {
|
|
44
|
+
const char = text.charCodeAt(i++);
|
|
45
|
+
k1 = k1 << 8 | char;
|
|
46
|
+
}
|
|
47
|
+
k1 = k1 * c1 >>> 0;
|
|
48
|
+
k1 = (k1 << r1 | k1 >>> 32 - r1) >>> 0;
|
|
49
|
+
k1 = k1 * c2 >>> 0;
|
|
50
|
+
h1 ^= k1;
|
|
51
|
+
h1 = (h1 << r2 | h1 >>> 32 - r2) >>> 0;
|
|
52
|
+
h1 = h1 * m + n >>> 0;
|
|
53
|
+
h1 ^= length;
|
|
54
|
+
h1 ^= h1 >>> 16;
|
|
55
|
+
h1 = h1 * 2246822507 >>> 0;
|
|
56
|
+
h1 ^= h1 >>> 13;
|
|
57
|
+
h1 = h1 * 3266489909 >>> 0;
|
|
58
|
+
h1 ^= h1 >>> 16;
|
|
59
|
+
return h1;
|
|
60
|
+
}
|
|
61
|
+
function getTextHash(text) {
|
|
62
|
+
return murmurHash3(text);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/core/color.ts
|
|
66
|
+
var ColorGenerator = class {
|
|
67
|
+
constructor(config = {}) {
|
|
68
|
+
__publicField(this, "config");
|
|
69
|
+
this.config = {
|
|
70
|
+
category: config.category || "default",
|
|
71
|
+
hue: config.hue || [0, 360],
|
|
72
|
+
saturation: config.saturation || [70, 100],
|
|
73
|
+
lightness: config.lightness || [40, 60]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Generate color from hash value
|
|
77
|
+
generateColor(hash) {
|
|
78
|
+
const { hue, saturation, lightness } = this.config;
|
|
79
|
+
const hueRange = hue[1] - hue[0];
|
|
80
|
+
const calculatedHue = hue[0] + hash % hueRange;
|
|
81
|
+
const satRange = saturation[1] - saturation[0];
|
|
82
|
+
const calculatedSat = saturation[0] + (hash >> 8) % satRange;
|
|
83
|
+
const lightRange = lightness[1] - lightness[0];
|
|
84
|
+
const calculatedLight = lightness[0] + (hash >> 16) % lightRange;
|
|
85
|
+
return `hsl(${calculatedHue}, ${calculatedSat}%, ${calculatedLight}%)`;
|
|
86
|
+
}
|
|
87
|
+
// Get color for text (wrapper method)
|
|
88
|
+
getColor(text, hashFn) {
|
|
89
|
+
const hash = hashFn(text);
|
|
90
|
+
return this.generateColor(hash);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/index.ts
|
|
95
|
+
var precomputedColors = {};
|
|
96
|
+
var ColorSet = class {
|
|
97
|
+
constructor(config = "default") {
|
|
98
|
+
__publicField(this, "config");
|
|
99
|
+
__publicField(this, "generator");
|
|
100
|
+
__publicField(this, "category");
|
|
101
|
+
if (typeof config === "string") {
|
|
102
|
+
this.config = { category: config };
|
|
103
|
+
} else {
|
|
104
|
+
this.config = config;
|
|
105
|
+
}
|
|
106
|
+
this.category = this.config.category || "default";
|
|
107
|
+
this.generator = new ColorGenerator(this.config);
|
|
108
|
+
}
|
|
109
|
+
// Get color for text
|
|
110
|
+
getColor(text) {
|
|
111
|
+
if (precomputedColors[this.category] && precomputedColors[this.category][text]) {
|
|
112
|
+
return precomputedColors[this.category][text];
|
|
113
|
+
}
|
|
114
|
+
const color = this.generator.getColor(text, getTextHash);
|
|
115
|
+
if (!precomputedColors[this.category]) {
|
|
116
|
+
precomputedColors[this.category] = {};
|
|
117
|
+
}
|
|
118
|
+
precomputedColors[this.category][text] = color;
|
|
119
|
+
return color;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function useAutoColor(config = "default") {
|
|
123
|
+
return new ColorSet(config);
|
|
124
|
+
}
|
|
125
|
+
function __internal__setPrecomputedColors(colors) {
|
|
126
|
+
Object.assign(precomputedColors, colors);
|
|
127
|
+
}
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
ColorSet,
|
|
131
|
+
__internal__setPrecomputedColors,
|
|
132
|
+
useAutoColor
|
|
133
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ColorGenerator,
|
|
3
|
+
__publicField,
|
|
4
|
+
getTextHash
|
|
5
|
+
} from "./chunk-GO6JHW6J.mjs";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
var precomputedColors = {};
|
|
9
|
+
var ColorSet = class {
|
|
10
|
+
constructor(config = "default") {
|
|
11
|
+
__publicField(this, "config");
|
|
12
|
+
__publicField(this, "generator");
|
|
13
|
+
__publicField(this, "category");
|
|
14
|
+
if (typeof config === "string") {
|
|
15
|
+
this.config = { category: config };
|
|
16
|
+
} else {
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
this.category = this.config.category || "default";
|
|
20
|
+
this.generator = new ColorGenerator(this.config);
|
|
21
|
+
}
|
|
22
|
+
// Get color for text
|
|
23
|
+
getColor(text) {
|
|
24
|
+
if (precomputedColors[this.category] && precomputedColors[this.category][text]) {
|
|
25
|
+
return precomputedColors[this.category][text];
|
|
26
|
+
}
|
|
27
|
+
const color = this.generator.getColor(text, getTextHash);
|
|
28
|
+
if (!precomputedColors[this.category]) {
|
|
29
|
+
precomputedColors[this.category] = {};
|
|
30
|
+
}
|
|
31
|
+
precomputedColors[this.category][text] = color;
|
|
32
|
+
return color;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function useAutoColor(config = "default") {
|
|
36
|
+
return new ColorSet(config);
|
|
37
|
+
}
|
|
38
|
+
function __internal__setPrecomputedColors(colors) {
|
|
39
|
+
Object.assign(precomputedColors, colors);
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
ColorSet,
|
|
43
|
+
__internal__setPrecomputedColors,
|
|
44
|
+
useAutoColor
|
|
45
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/vite-plugin.ts
|
|
23
|
+
var vite_plugin_exports = {};
|
|
24
|
+
__export(vite_plugin_exports, {
|
|
25
|
+
default: () => vite_plugin_default,
|
|
26
|
+
viteAutoColorPlugin: () => viteAutoColorPlugin
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(vite_plugin_exports);
|
|
29
|
+
var import_pluginutils = require("@rollup/pluginutils");
|
|
30
|
+
|
|
31
|
+
// src/core/hash.ts
|
|
32
|
+
function murmurHash3(text) {
|
|
33
|
+
let h1 = 3735928559;
|
|
34
|
+
const c1 = 3432918353;
|
|
35
|
+
const c2 = 461845907;
|
|
36
|
+
const r1 = 15;
|
|
37
|
+
const r2 = 13;
|
|
38
|
+
const m = 5;
|
|
39
|
+
const n = 3864292196;
|
|
40
|
+
let i = 0;
|
|
41
|
+
const length = text.length;
|
|
42
|
+
let k1 = 0;
|
|
43
|
+
while (i < length) {
|
|
44
|
+
const char = text.charCodeAt(i++);
|
|
45
|
+
k1 = k1 << 8 | char;
|
|
46
|
+
}
|
|
47
|
+
k1 = k1 * c1 >>> 0;
|
|
48
|
+
k1 = (k1 << r1 | k1 >>> 32 - r1) >>> 0;
|
|
49
|
+
k1 = k1 * c2 >>> 0;
|
|
50
|
+
h1 ^= k1;
|
|
51
|
+
h1 = (h1 << r2 | h1 >>> 32 - r2) >>> 0;
|
|
52
|
+
h1 = h1 * m + n >>> 0;
|
|
53
|
+
h1 ^= length;
|
|
54
|
+
h1 ^= h1 >>> 16;
|
|
55
|
+
h1 = h1 * 2246822507 >>> 0;
|
|
56
|
+
h1 ^= h1 >>> 13;
|
|
57
|
+
h1 = h1 * 3266489909 >>> 0;
|
|
58
|
+
h1 ^= h1 >>> 16;
|
|
59
|
+
return h1;
|
|
60
|
+
}
|
|
61
|
+
function getTextHash(text) {
|
|
62
|
+
return murmurHash3(text);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/core/color.ts
|
|
66
|
+
var ColorGenerator = class {
|
|
67
|
+
constructor(config = {}) {
|
|
68
|
+
__publicField(this, "config");
|
|
69
|
+
this.config = {
|
|
70
|
+
category: config.category || "default",
|
|
71
|
+
hue: config.hue || [0, 360],
|
|
72
|
+
saturation: config.saturation || [70, 100],
|
|
73
|
+
lightness: config.lightness || [40, 60]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Generate color from hash value
|
|
77
|
+
generateColor(hash) {
|
|
78
|
+
const { hue, saturation, lightness } = this.config;
|
|
79
|
+
const hueRange = hue[1] - hue[0];
|
|
80
|
+
const calculatedHue = hue[0] + hash % hueRange;
|
|
81
|
+
const satRange = saturation[1] - saturation[0];
|
|
82
|
+
const calculatedSat = saturation[0] + (hash >> 8) % satRange;
|
|
83
|
+
const lightRange = lightness[1] - lightness[0];
|
|
84
|
+
const calculatedLight = lightness[0] + (hash >> 16) % lightRange;
|
|
85
|
+
return `hsl(${calculatedHue}, ${calculatedSat}%, ${calculatedLight}%)`;
|
|
86
|
+
}
|
|
87
|
+
// Get color for text (wrapper method)
|
|
88
|
+
getColor(text, hashFn) {
|
|
89
|
+
const hash = hashFn(text);
|
|
90
|
+
return this.generateColor(hash);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/vite-plugin.ts
|
|
95
|
+
function viteAutoColorPlugin() {
|
|
96
|
+
const filter = (0, import_pluginutils.createFilter)(["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.vue"]);
|
|
97
|
+
const colorUsage = {};
|
|
98
|
+
return {
|
|
99
|
+
name: "v-auto-color",
|
|
100
|
+
// Analyze code during build
|
|
101
|
+
transform(code, id) {
|
|
102
|
+
if (!filter(id)) return null;
|
|
103
|
+
const useAutoColorRegex = /useAutoColor\s*\(\s*(?:(['"])([^'"]+)\1|\{[^}]*\})\s*\)/g;
|
|
104
|
+
const getColorRegex = /\.getColor\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
105
|
+
let match;
|
|
106
|
+
const colorSets = /* @__PURE__ */ new Set();
|
|
107
|
+
while ((match = useAutoColorRegex.exec(code)) !== null) {
|
|
108
|
+
let category = "default";
|
|
109
|
+
if (match[2]) {
|
|
110
|
+
category = match[2];
|
|
111
|
+
} else {
|
|
112
|
+
const configMatch = match[0].match(/category\s*:\s*['"]([^'"]+)['"]/);
|
|
113
|
+
if (configMatch) {
|
|
114
|
+
category = configMatch[1];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
colorSets.add(category);
|
|
118
|
+
}
|
|
119
|
+
const texts = /* @__PURE__ */ new Set();
|
|
120
|
+
while ((match = getColorRegex.exec(code)) !== null) {
|
|
121
|
+
texts.add(match[1]);
|
|
122
|
+
}
|
|
123
|
+
colorSets.forEach((category) => {
|
|
124
|
+
if (!colorUsage[category]) {
|
|
125
|
+
colorUsage[category] = {};
|
|
126
|
+
}
|
|
127
|
+
texts.forEach((text) => {
|
|
128
|
+
const generator = new ColorGenerator({ category });
|
|
129
|
+
const color = generator.getColor(text, getTextHash);
|
|
130
|
+
colorUsage[category][text] = color;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
return null;
|
|
134
|
+
},
|
|
135
|
+
// Generate precomputed colors module
|
|
136
|
+
generateBundle() {
|
|
137
|
+
const precomputedCode = `
|
|
138
|
+
import { __internal__setPrecomputedColors } from 'v-auto-color';
|
|
139
|
+
__internal__setPrecomputedColors(${JSON.stringify(colorUsage, null, 2)});
|
|
140
|
+
`;
|
|
141
|
+
this.emitFile({
|
|
142
|
+
type: "asset",
|
|
143
|
+
fileName: "v-auto-color-precomputed.js",
|
|
144
|
+
source: precomputedCode
|
|
145
|
+
});
|
|
146
|
+
this.emitFile({
|
|
147
|
+
type: "asset",
|
|
148
|
+
fileName: "v-auto-color-initializer.js",
|
|
149
|
+
source: `import "/v-auto-color-precomputed.js";`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
var vite_plugin_default = viteAutoColorPlugin;
|
|
155
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
156
|
+
0 && (module.exports = {
|
|
157
|
+
viteAutoColorPlugin
|
|
158
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ColorGenerator,
|
|
3
|
+
getTextHash
|
|
4
|
+
} from "./chunk-GO6JHW6J.mjs";
|
|
5
|
+
|
|
6
|
+
// src/vite-plugin.ts
|
|
7
|
+
import { createFilter } from "@rollup/pluginutils";
|
|
8
|
+
function viteAutoColorPlugin() {
|
|
9
|
+
const filter = createFilter(["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.vue"]);
|
|
10
|
+
const colorUsage = {};
|
|
11
|
+
return {
|
|
12
|
+
name: "v-auto-color",
|
|
13
|
+
// Analyze code during build
|
|
14
|
+
transform(code, id) {
|
|
15
|
+
if (!filter(id)) return null;
|
|
16
|
+
const useAutoColorRegex = /useAutoColor\s*\(\s*(?:(['"])([^'"]+)\1|\{[^}]*\})\s*\)/g;
|
|
17
|
+
const getColorRegex = /\.getColor\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
18
|
+
let match;
|
|
19
|
+
const colorSets = /* @__PURE__ */ new Set();
|
|
20
|
+
while ((match = useAutoColorRegex.exec(code)) !== null) {
|
|
21
|
+
let category = "default";
|
|
22
|
+
if (match[2]) {
|
|
23
|
+
category = match[2];
|
|
24
|
+
} else {
|
|
25
|
+
const configMatch = match[0].match(/category\s*:\s*['"]([^'"]+)['"]/);
|
|
26
|
+
if (configMatch) {
|
|
27
|
+
category = configMatch[1];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
colorSets.add(category);
|
|
31
|
+
}
|
|
32
|
+
const texts = /* @__PURE__ */ new Set();
|
|
33
|
+
while ((match = getColorRegex.exec(code)) !== null) {
|
|
34
|
+
texts.add(match[1]);
|
|
35
|
+
}
|
|
36
|
+
colorSets.forEach((category) => {
|
|
37
|
+
if (!colorUsage[category]) {
|
|
38
|
+
colorUsage[category] = {};
|
|
39
|
+
}
|
|
40
|
+
texts.forEach((text) => {
|
|
41
|
+
const generator = new ColorGenerator({ category });
|
|
42
|
+
const color = generator.getColor(text, getTextHash);
|
|
43
|
+
colorUsage[category][text] = color;
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
return null;
|
|
47
|
+
},
|
|
48
|
+
// Generate precomputed colors module
|
|
49
|
+
generateBundle() {
|
|
50
|
+
const precomputedCode = `
|
|
51
|
+
import { __internal__setPrecomputedColors } from 'v-auto-color';
|
|
52
|
+
__internal__setPrecomputedColors(${JSON.stringify(colorUsage, null, 2)});
|
|
53
|
+
`;
|
|
54
|
+
this.emitFile({
|
|
55
|
+
type: "asset",
|
|
56
|
+
fileName: "v-auto-color-precomputed.js",
|
|
57
|
+
source: precomputedCode
|
|
58
|
+
});
|
|
59
|
+
this.emitFile({
|
|
60
|
+
type: "asset",
|
|
61
|
+
fileName: "v-auto-color-initializer.js",
|
|
62
|
+
source: `import "/v-auto-color-precomputed.js";`
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
var vite_plugin_default = viteAutoColorPlugin;
|
|
68
|
+
export {
|
|
69
|
+
vite_plugin_default as default,
|
|
70
|
+
viteAutoColorPlugin
|
|
71
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="example-container">
|
|
3
|
+
<h1>v-auto-color 示例</h1>
|
|
4
|
+
|
|
5
|
+
<!-- 使用默认配置的颜色集 -->
|
|
6
|
+
<div class="color-set">
|
|
7
|
+
<h2>默认配置颜色集</h2>
|
|
8
|
+
<div
|
|
9
|
+
v-for="text in texts"
|
|
10
|
+
:key="text"
|
|
11
|
+
class="color-item"
|
|
12
|
+
:style="`--color: ${colorSet1.getColor(text)}`"
|
|
13
|
+
>
|
|
14
|
+
{{ text }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- 使用自定义配置的颜色集 -->
|
|
19
|
+
<div class="color-set">
|
|
20
|
+
<h2>自定义配置颜色集</h2>
|
|
21
|
+
<div
|
|
22
|
+
v-for="text in texts"
|
|
23
|
+
:key="text"
|
|
24
|
+
class="color-item"
|
|
25
|
+
:style="`--color: ${colorSet2.getColor(text)}`"
|
|
26
|
+
>
|
|
27
|
+
{{ text }}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import { useAutoColor } from 'v-auto-color';
|
|
35
|
+
|
|
36
|
+
// 默认配置的颜色集
|
|
37
|
+
const colorSet1 = useAutoColor('set1');
|
|
38
|
+
|
|
39
|
+
// 自定义配置的颜色集
|
|
40
|
+
const colorSet2 = useAutoColor({
|
|
41
|
+
category: 'set2',
|
|
42
|
+
hue: [180, 360], // 蓝色调范围
|
|
43
|
+
saturation: [60, 90],
|
|
44
|
+
lightness: [40, 70]
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 测试文本
|
|
48
|
+
const texts = [
|
|
49
|
+
'Hello World',
|
|
50
|
+
'Test Text',
|
|
51
|
+
'Another Example',
|
|
52
|
+
'Similar Text',
|
|
53
|
+
'Different Content'
|
|
54
|
+
];
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style scoped>
|
|
58
|
+
.example-container {
|
|
59
|
+
max-width: 800px;
|
|
60
|
+
margin: 0 auto;
|
|
61
|
+
padding: 20px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.color-set {
|
|
65
|
+
margin-bottom: 30px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.color-item {
|
|
69
|
+
display: inline-block;
|
|
70
|
+
padding: 10px 20px;
|
|
71
|
+
margin: 5px;
|
|
72
|
+
background-color: var(--color);
|
|
73
|
+
color: white;
|
|
74
|
+
border-radius: 4px;
|
|
75
|
+
font-weight: bold;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import vue from '@vitejs/plugin-vue';
|
|
3
|
+
import { viteAutoColorPlugin } from 'v-auto-color';
|
|
4
|
+
|
|
5
|
+
// https://vitejs.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [
|
|
8
|
+
vue(),
|
|
9
|
+
viteAutoColorPlugin() // 启用v-auto-color插件
|
|
10
|
+
],
|
|
11
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "v-auto-color",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Vite plugin for automatic color generation based on text similarity",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts src/vite-plugin.ts --format cjs,esm --dts",
|
|
17
|
+
"dev": "tsup src/index.ts src/vite-plugin.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"vite",
|
|
22
|
+
"plugin",
|
|
23
|
+
"color",
|
|
24
|
+
"auto-color",
|
|
25
|
+
"text-similarity",
|
|
26
|
+
"vue",
|
|
27
|
+
"typescript"
|
|
28
|
+
],
|
|
29
|
+
"author": "",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vite": "^4.0.0 || ^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@rollup/pluginutils": "^5.0.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^18.19.3",
|
|
39
|
+
"tsup": "^8.0.1",
|
|
40
|
+
"typescript": "^5.3.3",
|
|
41
|
+
"vite": "^5.0.10"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Color generation based on hash values
|
|
2
|
+
export interface ColorConfig {
|
|
3
|
+
category?: string;
|
|
4
|
+
hue?: [number, number];
|
|
5
|
+
saturation?: [number, number];
|
|
6
|
+
lightness?: [number, number];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ColorGenerator {
|
|
10
|
+
private config: Required<ColorConfig>;
|
|
11
|
+
|
|
12
|
+
constructor(config: ColorConfig = {}) {
|
|
13
|
+
this.config = {
|
|
14
|
+
category: config.category || 'default',
|
|
15
|
+
hue: config.hue || [0, 360],
|
|
16
|
+
saturation: config.saturation || [70, 100],
|
|
17
|
+
lightness: config.lightness || [40, 60]
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Generate color from hash value
|
|
22
|
+
generateColor(hash: number): string {
|
|
23
|
+
const { hue, saturation, lightness } = this.config;
|
|
24
|
+
|
|
25
|
+
// Calculate hue based on hash
|
|
26
|
+
const hueRange = hue[1] - hue[0];
|
|
27
|
+
const calculatedHue = hue[0] + (hash % hueRange);
|
|
28
|
+
|
|
29
|
+
// Calculate saturation based on hash
|
|
30
|
+
const satRange = saturation[1] - saturation[0];
|
|
31
|
+
const calculatedSat = saturation[0] + ((hash >> 8) % satRange);
|
|
32
|
+
|
|
33
|
+
// Calculate lightness based on hash
|
|
34
|
+
const lightRange = lightness[1] - lightness[0];
|
|
35
|
+
const calculatedLight = lightness[0] + ((hash >> 16) % lightRange);
|
|
36
|
+
|
|
37
|
+
// Return HSL color string
|
|
38
|
+
return `hsl(${calculatedHue}, ${calculatedSat}%, ${calculatedLight}%)`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get color for text (wrapper method)
|
|
42
|
+
getColor(text: string, hashFn: (text: string) => number): string {
|
|
43
|
+
const hash = hashFn(text);
|
|
44
|
+
return this.generateColor(hash);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/core/hash.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// MurmurHash3 implementation for text hashing
|
|
2
|
+
// Based on https://github.com/garycourt/murmurhash-js
|
|
3
|
+
|
|
4
|
+
export function murmurHash3(text: string): number {
|
|
5
|
+
let h1 = 0xdeadbeef; // Seed value
|
|
6
|
+
const c1 = 0xcc9e2d51;
|
|
7
|
+
const c2 = 0x1b873593;
|
|
8
|
+
const r1 = 15;
|
|
9
|
+
const r2 = 13;
|
|
10
|
+
const m = 5;
|
|
11
|
+
const n = 0xe6546b64;
|
|
12
|
+
|
|
13
|
+
let i = 0;
|
|
14
|
+
const length = text.length;
|
|
15
|
+
let k1 = 0;
|
|
16
|
+
|
|
17
|
+
while (i < length) {
|
|
18
|
+
const char = text.charCodeAt(i++);
|
|
19
|
+
k1 = (k1 << 8) | char;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
k1 = (k1 * c1) >>> 0;
|
|
23
|
+
k1 = ((k1 << r1) | (k1 >>> (32 - r1))) >>> 0;
|
|
24
|
+
k1 = (k1 * c2) >>> 0;
|
|
25
|
+
|
|
26
|
+
h1 ^= k1;
|
|
27
|
+
h1 = ((h1 << r2) | (h1 >>> (32 - r2))) >>> 0;
|
|
28
|
+
h1 = (h1 * m + n) >>> 0;
|
|
29
|
+
|
|
30
|
+
// Finalization
|
|
31
|
+
h1 ^= length;
|
|
32
|
+
h1 ^= h1 >>> 16;
|
|
33
|
+
h1 = (h1 * 0x85ebca6b) >>> 0;
|
|
34
|
+
h1 ^= h1 >>> 13;
|
|
35
|
+
h1 = (h1 * 0xc2b2ae35) >>> 0;
|
|
36
|
+
h1 ^= h1 >>> 16;
|
|
37
|
+
|
|
38
|
+
return h1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Generate a hash from text
|
|
42
|
+
export function getTextHash(text: string): number {
|
|
43
|
+
return murmurHash3(text);
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getTextHash } from './core/hash';
|
|
2
|
+
import { ColorConfig, ColorGenerator } from './core/color';
|
|
3
|
+
|
|
4
|
+
// Precomputed colors cache (filled by Vite plugin at build time)
|
|
5
|
+
const precomputedColors: Record<string, Record<string, string>> = {};
|
|
6
|
+
|
|
7
|
+
// ColorSet class for managing color generation
|
|
8
|
+
export class ColorSet {
|
|
9
|
+
private config: ColorConfig;
|
|
10
|
+
private generator: ColorGenerator;
|
|
11
|
+
private category: string;
|
|
12
|
+
|
|
13
|
+
constructor(config: ColorConfig | string = 'default') {
|
|
14
|
+
if (typeof config === 'string') {
|
|
15
|
+
this.config = { category: config };
|
|
16
|
+
} else {
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.category = this.config.category || 'default';
|
|
21
|
+
this.generator = new ColorGenerator(this.config);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get color for text
|
|
25
|
+
getColor(text: string): string {
|
|
26
|
+
// Check if color is precomputed
|
|
27
|
+
if (precomputedColors[this.category] && precomputedColors[this.category][text]) {
|
|
28
|
+
return precomputedColors[this.category][text];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Generate color at runtime if not precomputed
|
|
32
|
+
const color = this.generator.getColor(text, getTextHash);
|
|
33
|
+
|
|
34
|
+
// Cache the generated color for future use
|
|
35
|
+
if (!precomputedColors[this.category]) {
|
|
36
|
+
precomputedColors[this.category] = {};
|
|
37
|
+
}
|
|
38
|
+
precomputedColors[this.category][text] = color;
|
|
39
|
+
|
|
40
|
+
return color;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Main function to create ColorSet instances
|
|
45
|
+
export function useAutoColor(config: ColorConfig | string = 'default'): ColorSet {
|
|
46
|
+
return new ColorSet(config);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Expose precomputed colors for Vite plugin to fill
|
|
50
|
+
export function __internal__setPrecomputedColors(colors: Record<string, Record<string, string>>): void {
|
|
51
|
+
Object.assign(precomputedColors, colors);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Export types
|
|
55
|
+
export type { ColorConfig };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { createFilter } from '@rollup/pluginutils';
|
|
3
|
+
import { getTextHash } from './core/hash';
|
|
4
|
+
import { ColorGenerator } from './core/color';
|
|
5
|
+
|
|
6
|
+
// Vite plugin for precomputing colors at build time
|
|
7
|
+
export function viteAutoColorPlugin(): Plugin {
|
|
8
|
+
const filter = createFilter(['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue']);
|
|
9
|
+
|
|
10
|
+
// Collected color usage data
|
|
11
|
+
const colorUsage: Record<string, Record<string, string>> = {};
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
name: 'v-auto-color',
|
|
15
|
+
|
|
16
|
+
// Analyze code during build
|
|
17
|
+
transform(code, id) {
|
|
18
|
+
if (!filter(id)) return null;
|
|
19
|
+
|
|
20
|
+
// Extract useAutoColor calls and getColor calls
|
|
21
|
+
const useAutoColorRegex = /useAutoColor\s*\(\s*(?:(['"])([^'"]+)\1|\{[^}]*\})\s*\)/g;
|
|
22
|
+
const getColorRegex = /\.getColor\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
23
|
+
|
|
24
|
+
let match;
|
|
25
|
+
const colorSets = new Set<string>();
|
|
26
|
+
|
|
27
|
+
// Extract color set configurations
|
|
28
|
+
while ((match = useAutoColorRegex.exec(code)) !== null) {
|
|
29
|
+
let category = 'default';
|
|
30
|
+
if (match[2]) {
|
|
31
|
+
// String configuration
|
|
32
|
+
category = match[2];
|
|
33
|
+
} else {
|
|
34
|
+
// Object configuration - extract category
|
|
35
|
+
const configMatch = match[0].match(/category\s*:\s*['"]([^'"]+)['"]/);
|
|
36
|
+
if (configMatch) {
|
|
37
|
+
category = configMatch[1];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
colorSets.add(category);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Extract text parameters from getColor calls
|
|
44
|
+
const texts = new Set<string>();
|
|
45
|
+
while ((match = getColorRegex.exec(code)) !== null) {
|
|
46
|
+
texts.add(match[1]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Precompute colors for extracted texts
|
|
50
|
+
colorSets.forEach(category => {
|
|
51
|
+
if (!colorUsage[category]) {
|
|
52
|
+
colorUsage[category] = {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
texts.forEach(text => {
|
|
56
|
+
const generator = new ColorGenerator({ category });
|
|
57
|
+
const color = generator.getColor(text, getTextHash);
|
|
58
|
+
colorUsage[category][text] = color;
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Generate precomputed colors module
|
|
66
|
+
generateBundle() {
|
|
67
|
+
// Create precomputed colors code
|
|
68
|
+
const precomputedCode = `
|
|
69
|
+
import { __internal__setPrecomputedColors } from 'v-auto-color';
|
|
70
|
+
__internal__setPrecomputedColors(${JSON.stringify(colorUsage, null, 2)});
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
// Add precomputed colors module to bundle
|
|
74
|
+
this.emitFile({
|
|
75
|
+
type: 'asset',
|
|
76
|
+
fileName: 'v-auto-color-precomputed.js',
|
|
77
|
+
source: precomputedCode
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Ensure the precomputed module is loaded before application code
|
|
81
|
+
this.emitFile({
|
|
82
|
+
type: 'asset',
|
|
83
|
+
fileName: 'v-auto-color-initializer.js',
|
|
84
|
+
source: `import "/v-auto-color-precomputed.js";`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Export plugin
|
|
91
|
+
export default viteAutoColorPlugin;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"],
|
|
23
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
24
|
+
}
|