vome-core 0.0.99 → 0.0.100

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.
@@ -1 +1 @@
1
- const _0x1c475a=_0x213d;function _0x4f1c(){const _0x53ec3a=['z2L0zwu','odbWreThzg0','lY9Zzxj2AwnLlG','l2fWCc9WBhvNAw5tDg9Yzs9Zzwf0','Ahr0Chm6lY9NAxrODwiUy29TlW','mtGXmtm0ohLut2rmCa','zNjVBNrLBMq','mtuXode4ngzJrur1ua','Ahr0Chm6lY9NAxrLzs5JB20V','CMvWBgfJzq','mZaXnta0nvbqzgHxvG','DM9Tzs1WBhvNAw4TzNjVBNq','57QV5yMn56UV','mZC3nZqWmLfZrgziwa','DM9Tzs1WBhvNAw4TzNvSBa','5yMn56UVicSG5zco56UV','otmXn1HkB1D3uW','l3bSDwDPBNmVDxbSB2fK','DM9Tzs1WBhvNAw4TC2vYDMLJzq','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','ChjVDg9JB2W','57QV5zco56UV','6zkP5A2qic8G5PYn5yQH56UV5O+s5lU26isA5OMl5P6277YilNzVBwxVVjRKU4uGC2vYDMvY77Yj77Ym6ycc5zci5lIk5lYG44cb55+T5l+H44cb5PsV5lUy562j6io95yQB5OMP5Bgv44cc','mJC5nJe0ohDes3vYza','l2fWCc9WBhvNAw5tDg9Yzs93ywXSzxqVBgLJzw5Zzs9PC3n1zq','DM9TzwTQ','mZeZotKYmgDkr1b4uq','Ag9ZDg5HBwu'];_0x4f1c=function(){return _0x53ec3a;};return _0x4f1c();}(function(_0x544fe1,_0x220685){const _0x318160=_0x213d,_0x197a6a=_0x544fe1();while(!![]){try{const _0x5894e3=parseInt(_0x318160(0x91))/0x1+parseInt(_0x318160(0xa1))/0x2+-parseInt(_0x318160(0x97))/0x3+parseInt(_0x318160(0xab))/0x4+-parseInt(_0x318160(0x94))/0x5+-parseInt(_0x318160(0xa4))/0x6+-parseInt(_0x318160(0x9a))/0x7*(parseInt(_0x318160(0xa7))/0x8);if(_0x5894e3===_0x220685)break;else _0x197a6a['push'](_0x197a6a['shift']());}catch(_0x1a8493){_0x197a6a['push'](_0x197a6a['shift']());}}}(_0x4f1c,0xece52));const ORG=_0x1c475a(0xa3);export function serviceUrlFromDocs(_0x2104a1){const _0xf68399=_0x1c475a,_0x55a47=new URL(_0x2104a1),_0x52b384=_0x55a47[_0xf68399(0xa5)][_0xf68399(0x93)](/^www\./,'')[_0xf68399(0x93)](/^service\./,'');return _0x55a47[_0xf68399(0x9e)]+_0xf68399(0xa8)+_0x52b384;}export function seatApiBaseFromDocs(_0x4eff18){const _0x2cd62c=_0x1c475a;return serviceUrlFromDocs(_0x4eff18)+_0x2cd62c(0xa9);}export function licenseIssueUrlFromDocs(_0x3fe5d4){const _0x198efe=_0x1c475a;return serviceUrlFromDocs(_0x3fe5d4)+_0x198efe(0xa2);}function giteeArchive(_0x10f00f){return'https://gitee.com/'+ORG+'/'+_0x10f00f+'/repository/archive/master.zip';}function _0x213d(_0x2f7d3e,_0x5706ed){_0x2f7d3e=_0x2f7d3e-0x90;const _0x4f1c2d=_0x4f1c();let _0x213dd0=_0x4f1c2d[_0x2f7d3e];if(_0x213d['scmOUD']===undefined){var _0x18427f=function(_0x34fa42){const _0x1d9d70='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x2104a1='',_0x55a47='';for(let _0x52b384=0x0,_0x4eff18,_0x3fe5d4,_0x10f00f=0x0;_0x3fe5d4=_0x34fa42['charAt'](_0x10f00f++);~_0x3fe5d4&&(_0x4eff18=_0x52b384%0x4?_0x4eff18*0x40+_0x3fe5d4:_0x3fe5d4,_0x52b384++%0x4)?_0x2104a1+=String['fromCharCode'](0xff&_0x4eff18>>(-0x2*_0x52b384&0x6)):0x0){_0x3fe5d4=_0x1d9d70['indexOf'](_0x3fe5d4);}for(let _0x94e48d=0x0,_0x3a65fb=_0x2104a1['length'];_0x94e48d<_0x3a65fb;_0x94e48d++){_0x55a47+='%'+('00'+_0x2104a1['charCodeAt'](_0x94e48d)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x55a47);};_0x213d['lAJuVx']=_0x18427f,_0x213d['dqgAMf']={},_0x213d['scmOUD']=!![];}const _0x305698=_0x4f1c2d[0x0],_0x335753=_0x2f7d3e+_0x305698,_0x28bf1a=_0x213d['dqgAMf'][_0x335753];return!_0x28bf1a?(_0x213dd0=_0x213d['lAJuVx'](_0x213dd0),_0x213d['dqgAMf'][_0x335753]=_0x213dd0):_0x213dd0=_0x28bf1a,_0x213dd0;}function githubArchive(_0x94e48d){const _0x5c499f=_0x1c475a;return _0x5c499f(0xaa)+ORG+'/'+_0x94e48d+_0x5c499f(0x9d);}function giteeRepo(_0x3a65fb){const _0x19ef4d=_0x1c475a;return _0x19ef4d(0x92)+ORG+'/'+_0x3a65fb;}function githubRepo(_0x4539b3){const _0xafa380=_0x1c475a;return _0xafa380(0xaa)+ORG+'/'+_0x4539b3;}const docsUrl='https://vomekj.com';export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+_0x1c475a(0x9b),'mirrors':{'prefer':_0x1c475a(0xa6),'giteeOrg':_0x1c475a(0x92)+ORG,'githubOrg':_0x1c475a(0xaa)+ORG},'scaffolds':{'full':{'key':'full','title':_0x1c475a(0x99),'desc':'完整业务模块脚手架(.vome:含\x20server\x20与\x20web),本地开发打包后发布。','repo':_0x1c475a(0x98),'repoUrl':giteeRepo(_0x1c475a(0x98)),'githubRepoUrl':githubRepo(_0x1c475a(0x98)),'downloadUrl':giteeArchive(_0x1c475a(0x98)),'githubDownloadUrl':githubArchive(_0x1c475a(0x98))},'backend':{'key':'backend','title':_0x1c475a(0x9f),'desc':_0x1c475a(0xa0),'repo':_0x1c475a(0x9c),'repoUrl':giteeRepo('vome-plugin-service'),'githubRepoUrl':githubRepo(_0x1c475a(0x9c)),'downloadUrl':giteeArchive('vome-plugin-service'),'githubDownloadUrl':githubArchive('vome-plugin-service')},'frontend':{'key':_0x1c475a(0x90),'title':_0x1c475a(0x96),'desc':'后台微应用脚手架(.vome:含\x20web/),以\x20wujie\x20挂载独立前端页面。','repo':_0x1c475a(0x95),'repoUrl':giteeRepo(_0x1c475a(0x95)),'githubRepoUrl':githubRepo(_0x1c475a(0x95)),'downloadUrl':giteeArchive(_0x1c475a(0x95)),'githubDownloadUrl':githubArchive(_0x1c475a(0x95))}}};
1
+ const _0x3ff90c=_0x5f53;(function(_0x23faa9,_0x3572ce){const _0xa7d377=_0x5f53,_0x8dc756=_0x23faa9();while(!![]){try{const _0x142915=-parseInt(_0xa7d377(0x163))/0x1*(-parseInt(_0xa7d377(0x16b))/0x2)+-parseInt(_0xa7d377(0x171))/0x3+-parseInt(_0xa7d377(0x168))/0x4+-parseInt(_0xa7d377(0x165))/0x5+-parseInt(_0xa7d377(0x16d))/0x6*(-parseInt(_0xa7d377(0x16c))/0x7)+parseInt(_0xa7d377(0x173))/0x8*(-parseInt(_0xa7d377(0x161))/0x9)+-parseInt(_0xa7d377(0x16f))/0xa*(-parseInt(_0xa7d377(0x166))/0xb);if(_0x142915===_0x3572ce)break;else _0x8dc756['push'](_0x8dc756['shift']());}catch(_0x45a0a5){_0x8dc756['push'](_0x8dc756['shift']());}}}(_0x3f1f,0x930a9));function _0x5f53(_0x43315f,_0xc905dc){_0x43315f=_0x43315f-0x161;const _0x3f1f34=_0x3f1f();let _0x5f5342=_0x3f1f34[_0x43315f];if(_0x5f53['clGSmL']===undefined){var _0x101aee=function(_0x464ddb){const _0x4b4663='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x35dbb7='',_0x275b70='';for(let _0x12ab83=0x0,_0xc22f40,_0x4894b0,_0x3d5255=0x0;_0x4894b0=_0x464ddb['charAt'](_0x3d5255++);~_0x4894b0&&(_0xc22f40=_0x12ab83%0x4?_0xc22f40*0x40+_0x4894b0:_0x4894b0,_0x12ab83++%0x4)?_0x35dbb7+=String['fromCharCode'](0xff&_0xc22f40>>(-0x2*_0x12ab83&0x6)):0x0){_0x4894b0=_0x4b4663['indexOf'](_0x4894b0);}for(let _0x392816=0x0,_0xea8127=_0x35dbb7['length'];_0x392816<_0xea8127;_0x392816++){_0x275b70+='%'+('00'+_0x35dbb7['charCodeAt'](_0x392816)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x275b70);};_0x5f53['RkKRtU']=_0x101aee,_0x5f53['UELVwS']={},_0x5f53['clGSmL']=!![];}const _0x226af0=_0x3f1f34[0x0],_0x3f6264=_0x43315f+_0x226af0,_0x35b9fb=_0x5f53['UELVwS'][_0x3f6264];return!_0x35b9fb?(_0x5f5342=_0x5f53['RkKRtU'](_0x5f5342),_0x5f53['UELVwS'][_0x3f6264]=_0x5f5342):_0x5f5342=_0x35b9fb,_0x5f5342;}const ORG=_0x3ff90c(0x167);export function serviceUrlFromDocs(_0x35dbb7){const _0x3faaab=_0x3ff90c,_0x275b70=new URL(_0x35dbb7),_0x12ab83=_0x275b70['hostname'][_0x3faaab(0x17c)](/^www\./,'')[_0x3faaab(0x17c)](/^service\./,'');return _0x275b70[_0x3faaab(0x16e)]+_0x3faaab(0x17b)+_0x12ab83;}export function seatApiBaseFromDocs(_0xc22f40){return serviceUrlFromDocs(_0xc22f40)+'/app/pluginStore/seat';}export function licenseIssueUrlFromDocs(_0x4894b0){const _0x36d9fa=_0x3ff90c;return serviceUrlFromDocs(_0x4894b0)+_0x36d9fa(0x164);}function giteeArchive(_0x3d5255){const _0x228479=_0x3ff90c;return'https://gitee.com/'+ORG+'/'+_0x3d5255+_0x228479(0x17a);}function githubArchive(_0x392816){const _0x5bcdee=_0x3ff90c;return _0x5bcdee(0x169)+ORG+'/'+_0x392816+_0x5bcdee(0x179);}function giteeRepo(_0xea8127){return'https://gitee.com/'+ORG+'/'+_0xea8127;}function githubRepo(_0x32dc63){return'https://github.com/'+ORG+'/'+_0x32dc63;}function _0x3f1f(){const _0x2631af=['57QV5yMn56UV','DM9Tzs1WBhvNAw4TzNjVBNq','mJa4mZqXy3POALbz','DM9Tzs1WBhvNAw4TC2vYDMLJzq','mNzuyuzwBq','l2fWCc9WBhvNAw5tDg9Yzs93ywXSzxqVBgLJzw5Zzs9PC3n1zq','ntiZmdeZmgvdsgLVAq','mtfRtuPTsKW','DM9TzwTQ','mJy5mdKYoeLwEerzvq','Ahr0Chm6lY9NAxrODwiUy29TlW','Ahr0Chm6lY9NAxrLzs5JB20V','nZeWodzZyKnMzw0','mJeYnty5y2zrBLzw','nKDXt09vtG','ChjVDg9JB2W','mZu5mde1ndbOu0rjBfa','zNvSBa','mJm3ntuYowL5BNvXCG','5yMn56UVicSG5zco56UV','mJaWD1HzqwDz','DM9Tzs1WBhvNAw4TzNvSBa','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','l3bSDwDPBNmVDxbSB2fK','zNjVBNrLBMq','yMfJA2vUza','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','l3jLCg9ZAxrVCNKVyxjJAgL2zs9Tyxn0zxiUEMLW','lY9Zzxj2AwnLlG','CMvWBgfJzq','Ahr0Chm6lY92B21LA2OUy29T','57QV5zco56UV'];_0x3f1f=function(){return _0x2631af;};return _0x3f1f();}const docsUrl=_0x3ff90c(0x17d);export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+_0x3ff90c(0x176),'mirrors':{'prefer':'gitee','giteeOrg':_0x3ff90c(0x16a)+ORG,'githubOrg':_0x3ff90c(0x169)+ORG},'scaffolds':{'full':{'key':_0x3ff90c(0x170),'title':_0x3ff90c(0x172),'desc':_0x3ff90c(0x175),'repo':_0x3ff90c(0x174),'repoUrl':giteeRepo(_0x3ff90c(0x174)),'githubRepoUrl':githubRepo(_0x3ff90c(0x174)),'downloadUrl':giteeArchive(_0x3ff90c(0x174)),'githubDownloadUrl':githubArchive(_0x3ff90c(0x174))},'backend':{'key':_0x3ff90c(0x178),'title':_0x3ff90c(0x17e),'desc':'钩子\x20/\x20服务端插件脚手架(.vome:仅\x20server),适合上传、短信、支付等能力扩展。','repo':_0x3ff90c(0x162),'repoUrl':giteeRepo(_0x3ff90c(0x162)),'githubRepoUrl':githubRepo(_0x3ff90c(0x162)),'downloadUrl':giteeArchive('vome-plugin-service'),'githubDownloadUrl':githubArchive(_0x3ff90c(0x162))},'frontend':{'key':_0x3ff90c(0x177),'title':_0x3ff90c(0x17f),'desc':'后台微应用脚手架(.vome:含\x20web/),以\x20wujie\x20挂载独立前端页面。','repo':'vome-plugin-front','repoUrl':giteeRepo(_0x3ff90c(0x180)),'githubRepoUrl':githubRepo(_0x3ff90c(0x180)),'downloadUrl':giteeArchive(_0x3ff90c(0x180)),'githubDownloadUrl':githubArchive('vome-plugin-front')}}};
@@ -67,7 +67,19 @@
67
67
  title="吸取颜色"
68
68
  @click="pickEyeDropper"
69
69
  >
70
- <i class="ri-eyedropper-line" aria-hidden="true" />
70
+ <!-- 内联 SVG:不依赖 remixicon 字体是否已加载 -->
71
+ <svg
72
+ class="vm-color-picker__eyedropper-ico"
73
+ viewBox="0 0 24 24"
74
+ width="16"
75
+ height="16"
76
+ aria-hidden="true"
77
+ >
78
+ <path
79
+ fill="currentColor"
80
+ d="M19.07 3.66a2.5 2.5 0 0 1 0 3.54l-1.42 1.41 2.12 2.12-1.41 1.42-2.12-2.13-7.07 7.07c-.3.3-.68.48-1.08.54l-3.36.48a.75.75 0 0 1-.84-.84l.48-3.36c.06-.4.24-.78.54-1.08l7.07-7.07-1.41-1.41 1.41-1.42 2.12 2.13 1.42-1.42a2.5 2.5 0 0 1 3.54 0Zm-2.12 2.12.71-.7a1 1 0 0 0-1.42-1.42l-.7.71 1.41 1.41ZM8.48 15.9l-.17.17-.28 1.9 1.9-.28.17-.17 5.66-5.66-1.62-1.62-5.66 5.66Z"
81
+ />
82
+ </svg>
71
83
  </button>
72
84
  <div class="vm-color-picker__slider-col">
73
85
  <div
@@ -77,7 +89,7 @@
77
89
  >
78
90
  <span
79
91
  class="vm-color-picker__slider-thumb"
80
- :style="{ left: `${(hue / 360) * 100}%` }"
92
+ :style="{ left: thumbLeft(hue / 360) }"
81
93
  />
82
94
  </div>
83
95
  <div
@@ -91,25 +103,57 @@
91
103
  />
92
104
  <span
93
105
  class="vm-color-picker__slider-thumb"
94
- :style="{ left: `${alpha * 100}%` }"
106
+ :style="{ left: thumbLeft(alpha) }"
95
107
  />
96
108
  </div>
97
109
  </div>
98
110
  </div>
99
111
 
100
112
  <div class="vm-color-picker__footer">
101
- <span class="vm-color-picker__format" title="颜色格式">
102
- Hex
103
- <i class="ri-arrow-down-s-line" aria-hidden="true" />
104
- </span>
113
+ <div class="vm-color-picker__format-wrap">
114
+ <button
115
+ type="button"
116
+ class="vm-color-picker__format"
117
+ :class="{ 'is-open': formatOpen }"
118
+ title="颜色格式"
119
+ @click.stop="toggleFormatMenu"
120
+ >
121
+ {{ formatLabel }}
122
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
123
+ <path fill="currentColor" d="M12 15.4 6.6 10l1.4-1.4 4 4 4-4L17.4 10 12 15.4Z" />
124
+ </svg>
125
+ </button>
126
+ <div v-if="formatOpen" class="vm-color-picker__format-menu" role="listbox">
127
+ <button
128
+ v-for="opt in FORMAT_OPTS"
129
+ :key="opt.id"
130
+ type="button"
131
+ class="vm-color-picker__format-item"
132
+ :class="{ 'is-active': colorFormat === opt.id }"
133
+ role="option"
134
+ :aria-selected="colorFormat === opt.id"
135
+ @click.stop="setFormat(opt.id)"
136
+ >
137
+ <span class="vm-color-picker__format-check" aria-hidden="true">
138
+ <svg v-if="colorFormat === opt.id" viewBox="0 0 24 24" width="14" height="14">
139
+ <path
140
+ fill="currentColor"
141
+ d="M9.55 17.6 4.9 12.95l1.4-1.4 3.25 3.25 7.15-7.15 1.4 1.4-8.55 8.55Z"
142
+ />
143
+ </svg>
144
+ </span>
145
+ {{ opt.label }}
146
+ </button>
147
+ </div>
148
+ </div>
105
149
  <input
106
150
  type="text"
107
- class="vm-color-picker__hex"
108
- :value="panelHex"
151
+ class="vm-color-picker__value"
152
+ :value="panelValue"
109
153
  spellcheck="false"
110
154
  autocomplete="off"
111
- @input="onPanelHexInput"
112
- @blur="onPanelHexBlur"
155
+ @input="onPanelValueInput"
156
+ @blur="onPanelValueBlur"
113
157
  @keydown.esc.stop="closePanel"
114
158
  />
115
159
  <input
@@ -155,6 +199,17 @@ const emit = defineEmits<{
155
199
 
156
200
  type Rgba = { r: number; g: number; b: number; a: number }
157
201
  type Hsv = { h: number; s: number; v: number }
202
+ type ColorFormat = 'hex' | 'rgb' | 'css' | 'hsl' | 'hsb'
203
+
204
+ const FORMAT_OPTS: { id: ColorFormat; label: string }[] = [
205
+ { id: 'hex', label: 'Hex' },
206
+ { id: 'rgb', label: 'RGB' },
207
+ { id: 'css', label: 'CSS' },
208
+ { id: 'hsl', label: 'HSL' },
209
+ { id: 'hsb', label: 'HSB' },
210
+ ]
211
+
212
+ const THUMB_PX = 14
158
213
 
159
214
  const rootRef = ref<HTMLElement | null>(null)
160
215
  const triggerRef = ref<HTMLElement | null>(null)
@@ -166,15 +221,17 @@ const alphaRef = ref<HTMLElement | null>(null)
166
221
  const open = ref(false)
167
222
  const suppressOpen = ref(false)
168
223
  const panelStyle = ref<Record<string, string>>({})
224
+ const formatOpen = ref(false)
225
+ const colorFormat = ref<ColorFormat>('hex')
169
226
 
170
227
  const hue = ref(0)
171
228
  const sat = ref(0)
172
229
  const val = ref(0)
173
230
  const alpha = ref(1)
174
231
 
175
- /** 行内 / 面板 Hex 编辑中的草稿(不含 #) */
232
+ /** 行内 Hex(不含 #);面板按当前格式展示 */
176
233
  const inlineHex = ref('')
177
- const panelHex = ref('')
234
+ const panelValue = ref('')
178
235
  const alphaPctText = ref('100%')
179
236
 
180
237
  const hasEyeDropper = ref(false)
@@ -184,10 +241,20 @@ const placeholderText = computed(() => {
184
241
  return p.startsWith('#') ? p.slice(1) : p
185
242
  })
186
243
 
244
+ const formatLabel = computed(
245
+ () => FORMAT_OPTS.find((o) => o.id === colorFormat.value)?.label ?? 'Hex',
246
+ )
247
+
187
248
  function clamp(n: number, min: number, max: number) {
188
249
  return Math.min(max, Math.max(min, n))
189
250
  }
190
251
 
252
+ /** 拇指中心落在轨道内,两端不溢出 */
253
+ function thumbLeft(ratio: number) {
254
+ const t = clamp(ratio, 0, 1)
255
+ return `calc(${t} * (100% - ${THUMB_PX}px) + ${THUMB_PX / 2}px)`
256
+ }
257
+
191
258
  function toHex2(n: number) {
192
259
  return clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0')
193
260
  }
@@ -251,6 +318,61 @@ function rgbToHsv(r: number, g: number, b: number): Hsv {
251
318
  return { h, s, v: max }
252
319
  }
253
320
 
321
+ function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
322
+ const rr = r / 255
323
+ const gg = g / 255
324
+ const bb = b / 255
325
+ const max = Math.max(rr, gg, bb)
326
+ const min = Math.min(rr, gg, bb)
327
+ const l = (max + min) / 2
328
+ const d = max - min
329
+ let h = 0
330
+ let s = 0
331
+ if (d !== 0) {
332
+ s = d / (1 - Math.abs(2 * l - 1))
333
+ if (max === rr) h = ((gg - bb) / d) % 6
334
+ else if (max === gg) h = (bb - rr) / d + 2
335
+ else h = (rr - gg) / d + 4
336
+ h *= 60
337
+ if (h < 0) h += 360
338
+ }
339
+ return { h, s, l }
340
+ }
341
+
342
+ function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
343
+ const hh = ((h % 360) + 360) % 360
344
+ const c = (1 - Math.abs(2 * l - 1)) * s
345
+ const x = c * (1 - Math.abs(((hh / 60) % 2) - 1))
346
+ const m = l - c / 2
347
+ let rp = 0
348
+ let gp = 0
349
+ let bp = 0
350
+ if (hh < 60) {
351
+ rp = c
352
+ gp = x
353
+ } else if (hh < 120) {
354
+ rp = x
355
+ gp = c
356
+ } else if (hh < 180) {
357
+ gp = c
358
+ bp = x
359
+ } else if (hh < 240) {
360
+ gp = x
361
+ bp = c
362
+ } else if (hh < 300) {
363
+ rp = x
364
+ bp = c
365
+ } else {
366
+ rp = c
367
+ bp = x
368
+ }
369
+ return {
370
+ r: (rp + m) * 255,
371
+ g: (gp + m) * 255,
372
+ b: (bp + m) * 255,
373
+ }
374
+ }
375
+
254
376
  /** 解析为 rgba;无效返回 null */
255
377
  function parseColor(raw: string): Rgba | null {
256
378
  const s = raw.trim()
@@ -283,9 +405,8 @@ function parseColor(raw: string): Rgba | null {
283
405
  return null
284
406
  }
285
407
 
286
- const bare = s.startsWith('#') ? s.slice(1) : s
287
- if (/^[0-9a-fA-F]{3}$/.test(bare) || /^[0-9a-fA-F]{6}$/.test(bare) || /^[0-9a-fA-F]{8}$/.test(bare)) {
288
- return parseColor(`#${bare}`)
408
+ if (/^[0-9a-fA-F]{3}$/.test(s) || /^[0-9a-fA-F]{6}$/.test(s) || /^[0-9a-fA-F]{8}$/.test(s)) {
409
+ return parseColor(`#${s}`)
289
410
  }
290
411
 
291
412
  const m = s.match(
@@ -299,6 +420,68 @@ function parseColor(raw: string): Rgba | null {
299
420
  a: m[4] != null ? clamp(Number(m[4]), 0, 1) : 1,
300
421
  }
301
422
  }
423
+
424
+ const hsl = s.match(
425
+ /^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*([\d.]+))?\s*\)$/i,
426
+ )
427
+ if (hsl) {
428
+ const rgb = hslToRgb(Number(hsl[1]), Number(hsl[2]) / 100, Number(hsl[3]) / 100)
429
+ return {
430
+ r: rgb.r,
431
+ g: rgb.g,
432
+ b: rgb.b,
433
+ a: hsl[4] != null ? clamp(Number(hsl[4]), 0, 1) : 1,
434
+ }
435
+ }
436
+
437
+ return null
438
+ }
439
+
440
+ /** 按当前格式解析面板中间输入(不改变 alpha,除非 CSS/rgba 自带) */
441
+ function parsePanelValue(raw: string, keepAlpha: number): Rgba | null {
442
+ const s = raw.trim()
443
+ if (!s) return null
444
+ const fmt = colorFormat.value
445
+
446
+ if (fmt === 'hex' || fmt === 'css') {
447
+ const parsed = parseColor(s)
448
+ if (!parsed) return null
449
+ if (fmt === 'hex') parsed.a = keepAlpha
450
+ return parsed
451
+ }
452
+
453
+ if (fmt === 'rgb') {
454
+ const parts = s.split(/[,\s]+/).filter(Boolean)
455
+ if (parts.length < 3) return null
456
+ const r = Number(parts[0])
457
+ const g = Number(parts[1])
458
+ const b = Number(parts[2])
459
+ if (![r, g, b].every(Number.isFinite)) return null
460
+ return { r: clamp(r, 0, 255), g: clamp(g, 0, 255), b: clamp(b, 0, 255), a: keepAlpha }
461
+ }
462
+
463
+ if (fmt === 'hsl') {
464
+ const parts = s.replace(/%/g, '').split(/[,\s]+/).filter(Boolean)
465
+ if (parts.length < 3) return null
466
+ const h = Number(parts[0])
467
+ const satPct = Number(parts[1])
468
+ const lPct = Number(parts[2])
469
+ if (![h, satPct, lPct].every(Number.isFinite)) return null
470
+ const rgb = hslToRgb(h, clamp(satPct, 0, 100) / 100, clamp(lPct, 0, 100) / 100)
471
+ return { ...rgb, a: keepAlpha }
472
+ }
473
+
474
+ if (fmt === 'hsb') {
475
+ const parts = s.replace(/%/g, '').split(/[,\s]+/).filter(Boolean)
476
+ if (parts.length < 3) return null
477
+ const h = Number(parts[0])
478
+ const sPct = Number(parts[1])
479
+ const bPct = Number(parts[2])
480
+ if (![h, sPct, bPct].every(Number.isFinite)) return null
481
+ const rgb = hsvToRgb(h, clamp(sPct, 0, 100) / 100, clamp(bPct, 0, 100) / 100)
482
+ return { ...rgb, a: keepAlpha }
483
+ }
484
+
302
485
  return null
303
486
  }
304
487
 
@@ -311,6 +494,31 @@ function currentHex(): string {
311
494
  return rgbToHex(r, g, b, alpha.value)
312
495
  }
313
496
 
497
+ function formatPanelValue(): string {
498
+ const { r, g, b } = currentRgb()
499
+ const rr = Math.round(r)
500
+ const gg = Math.round(g)
501
+ const bb = Math.round(b)
502
+ const a = alpha.value
503
+ switch (colorFormat.value) {
504
+ case 'rgb':
505
+ return `${rr}, ${gg}, ${bb}`
506
+ case 'css':
507
+ return a < 1 - 1e-6
508
+ ? `rgba(${rr}, ${gg}, ${bb}, ${Number(a.toFixed(2))})`
509
+ : `#${toHex2(rr)}${toHex2(gg)}${toHex2(bb)}`
510
+ case 'hsl': {
511
+ const hsl = rgbToHsl(rr, gg, bb)
512
+ return `${Math.round(hsl.h)}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%`
513
+ }
514
+ case 'hsb':
515
+ return `${Math.round(hue.value)}, ${Math.round(sat.value * 100)}%, ${Math.round(val.value * 100)}%`
516
+ case 'hex':
517
+ default:
518
+ return `${toHex2(rr)}${toHex2(gg)}${toHex2(bb)}`
519
+ }
520
+ }
521
+
314
522
  const cssColor = computed(() => {
315
523
  const parsed = parseColor(props.modelValue ?? '')
316
524
  if (!parsed && !(props.modelValue ?? '').trim()) return 'transparent'
@@ -338,9 +546,8 @@ const svCursorStyle = computed(() => ({
338
546
  function syncTextsFromState() {
339
547
  const hex = currentHex()
340
548
  const bare = hex.startsWith('#') ? hex.slice(1) : hex
341
- // 行内只展示 RGB 六位;透明度在面板 % 里改
342
549
  inlineHex.value = bare.slice(0, 6)
343
- panelHex.value = bare.slice(0, 6)
550
+ panelValue.value = formatPanelValue()
344
551
  alphaPctText.value = `${Math.round(alpha.value * 100)}%`
345
552
  }
346
553
 
@@ -362,7 +569,7 @@ function loadFromModel(raw: string | null | undefined) {
362
569
  val.value = 0
363
570
  alpha.value = 1
364
571
  inlineHex.value = ''
365
- panelHex.value = ''
572
+ panelValue.value = ''
366
573
  alphaPctText.value = '100%'
367
574
  return
368
575
  }
@@ -383,7 +590,6 @@ watch(
383
590
  () => props.modelValue,
384
591
  (v) => {
385
592
  if (open.value) {
386
- // 面板打开时若外部写入且与当前一致则跳过抖动
387
593
  const cur = currentHex()
388
594
  const parsed = parseColor(v ?? '')
389
595
  if (parsed && rgbToHex(parsed.r, parsed.g, parsed.b, parsed.a) === cur) return
@@ -413,7 +619,7 @@ function onInlineInput(e: Event) {
413
619
  hue.value = hsv.h
414
620
  sat.value = hsv.s
415
621
  val.value = hsv.v
416
- panelHex.value = inlineHex.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 6)
622
+ panelValue.value = formatPanelValue()
417
623
  commit(currentHex())
418
624
  } else {
419
625
  const raw = inlineHex.value.trim()
@@ -437,25 +643,25 @@ function onInlineBlur() {
437
643
  }
438
644
  }
439
645
 
440
- function onPanelHexInput(e: Event) {
441
- const v = (e.target as HTMLInputElement).value.replace(/^#/, '')
442
- panelHex.value = v
443
- const parsed = parseColor(v)
444
- if (parsed) {
445
- parsed.a = alpha.value
446
- const hsv = rgbToHsv(parsed.r, parsed.g, parsed.b)
447
- hue.value = hsv.h
448
- sat.value = hsv.s
449
- val.value = hsv.v
450
- inlineHex.value = v.replace(/[^0-9a-fA-F]/g, '').slice(0, 6)
451
- commit(currentHex())
452
- }
646
+ function onPanelValueInput(e: Event) {
647
+ panelValue.value = (e.target as HTMLInputElement).value
648
+ const parsed = parsePanelValue(panelValue.value, alpha.value)
649
+ if (!parsed) return
650
+ const hsv = rgbToHsv(parsed.r, parsed.g, parsed.b)
651
+ hue.value = hsv.h
652
+ sat.value = hsv.s
653
+ val.value = hsv.v
654
+ if (colorFormat.value === 'css') alpha.value = parsed.a
655
+ const hex = currentHex()
656
+ inlineHex.value = hex.replace(/^#/, '').slice(0, 6)
657
+ alphaPctText.value = `${Math.round(alpha.value * 100)}%`
658
+ commit(hex)
453
659
  }
454
660
 
455
- function onPanelHexBlur() {
456
- const parsed = parseColor(panelHex.value)
661
+ function onPanelValueBlur() {
662
+ const parsed = parsePanelValue(panelValue.value, alpha.value)
457
663
  if (parsed) {
458
- parsed.a = alpha.value
664
+ if (colorFormat.value !== 'css') parsed.a = alpha.value
459
665
  applyRgba(parsed, true)
460
666
  } else {
461
667
  syncTextsFromState()
@@ -479,6 +685,16 @@ function onAlphaPctBlur() {
479
685
  commit(currentHex())
480
686
  }
481
687
 
688
+ function toggleFormatMenu() {
689
+ formatOpen.value = !formatOpen.value
690
+ }
691
+
692
+ function setFormat(id: ColorFormat) {
693
+ colorFormat.value = id
694
+ formatOpen.value = false
695
+ syncTextsFromState()
696
+ }
697
+
482
698
  function ratioFromEvent(el: HTMLElement, e: PointerEvent, axis: 'x' | 'y' | 'both') {
483
699
  const r = el.getBoundingClientRect()
484
700
  const x = clamp((e.clientX - r.left) / r.width, 0, 1)
@@ -540,7 +756,11 @@ function onAlphaPointer(e: PointerEvent) {
540
756
  }
541
757
 
542
758
  async function pickEyeDropper() {
543
- const ED = (window as unknown as { EyeDropper?: new () => { open: () => Promise<{ sRGBHex: string }> } }).EyeDropper
759
+ const ED = (
760
+ window as unknown as {
761
+ EyeDropper?: new () => { open: () => Promise<{ sRGBHex: string }> }
762
+ }
763
+ ).EyeDropper
544
764
  if (!ED) return
545
765
  try {
546
766
  const result = await new ED().open()
@@ -564,7 +784,7 @@ function syncPanelPos() {
564
784
  left = Math.max(8, window.innerWidth - width - 8)
565
785
  }
566
786
  let top = r.bottom + 6
567
- const estH = 280
787
+ const estH = 320
568
788
  if (top + estH > window.innerHeight - 8) {
569
789
  top = Math.max(8, r.top - estH - 6)
570
790
  }
@@ -580,6 +800,7 @@ function syncPanelPos() {
580
800
  async function openPanel() {
581
801
  if (props.disabled || suppressOpen.value) return
582
802
  loadFromModel(props.modelValue)
803
+ formatOpen.value = false
583
804
  open.value = true
584
805
  await nextTick()
585
806
  syncPanelPos()
@@ -592,6 +813,7 @@ function onPanelUnmountAutoFocus(event: Event) {
592
813
  function closePanel() {
593
814
  if (!open.value) return
594
815
  suppressOpen.value = true
816
+ formatOpen.value = false
595
817
  open.value = false
596
818
  nextTick(() => {
597
819
  requestAnimationFrame(() => {
@@ -623,9 +845,7 @@ function onWinChange() {
623
845
  }
624
846
 
625
847
  onMounted(() => {
626
- hasEyeDropper.value =
627
- typeof window !== 'undefined' &&
628
- 'EyeDropper' in window
848
+ hasEyeDropper.value = typeof window !== 'undefined' && 'EyeDropper' in window
629
849
  document.addEventListener('pointerdown', onDocPointer, true)
630
850
  window.addEventListener('resize', onWinChange)
631
851
  window.addEventListener('scroll', onWinChange, true)
@@ -741,6 +961,7 @@ onUnmounted(() => {
741
961
  0 4px 6px -1px rgb(0 0 0 / 10%),
742
962
  0 10px 24px -4px rgb(0 0 0 / 12%);
743
963
  user-select: none;
964
+ overflow: visible;
744
965
  }
745
966
 
746
967
  .vm-color-picker__sv {
@@ -783,7 +1004,6 @@ onUnmounted(() => {
783
1004
  border-radius: 6px;
784
1005
  background: var(--muted);
785
1006
  color: var(--foreground);
786
- font-size: 15px;
787
1007
  cursor: pointer;
788
1008
 
789
1009
  &:hover {
@@ -792,6 +1012,10 @@ onUnmounted(() => {
792
1012
  }
793
1013
  }
794
1014
 
1015
+ .vm-color-picker__eyedropper-ico {
1016
+ display: block;
1017
+ }
1018
+
795
1019
  .vm-color-picker__slider-col {
796
1020
  flex: 1;
797
1021
  min-width: 0;
@@ -807,6 +1031,8 @@ onUnmounted(() => {
807
1031
  border-radius: 999px;
808
1032
  cursor: pointer;
809
1033
  touch-action: none;
1034
+ /* 允许拇指画出轨道高度,但水平用 thumbLeft 钳在内 */
1035
+ overflow: visible;
810
1036
  }
811
1037
 
812
1038
  .vm-color-picker__hue {
@@ -835,7 +1061,6 @@ onUnmounted(() => {
835
1061
  4px -4px,
836
1062
  -4px 0;
837
1063
  background-color: #fff;
838
- overflow: hidden;
839
1064
  }
840
1065
 
841
1066
  .vm-color-picker__alpha-fill {
@@ -843,6 +1068,7 @@ onUnmounted(() => {
843
1068
  inset: 0;
844
1069
  border-radius: inherit;
845
1070
  pointer-events: none;
1071
+ overflow: hidden;
846
1072
  }
847
1073
 
848
1074
  .vm-color-picker__slider-thumb {
@@ -850,43 +1076,102 @@ onUnmounted(() => {
850
1076
  top: 50%;
851
1077
  width: 14px;
852
1078
  height: 14px;
853
- margin: -7px 0 0 -7px;
1079
+ transform: translate(-50%, -50%);
854
1080
  border: 2px solid #fff;
855
1081
  border-radius: 50%;
856
1082
  background: transparent;
857
1083
  box-shadow: 0 0 0 1px rgb(0 0 0 / 35%);
858
1084
  pointer-events: none;
1085
+ box-sizing: border-box;
859
1086
  }
860
1087
 
861
1088
  .vm-color-picker__footer {
1089
+ position: relative;
862
1090
  display: flex;
863
1091
  align-items: center;
864
1092
  gap: 6px;
865
1093
  margin-top: 12px;
866
1094
  }
867
1095
 
868
- .vm-color-picker__format {
1096
+ .vm-color-picker__format-wrap {
1097
+ position: relative;
869
1098
  flex-shrink: 0;
1099
+ }
1100
+
1101
+ .vm-color-picker__format {
870
1102
  display: inline-flex;
871
1103
  align-items: center;
872
1104
  gap: 2px;
873
1105
  height: 28px;
874
- padding: 0 6px;
1106
+ padding: 0 8px;
875
1107
  border: 1px solid var(--border);
876
1108
  border-radius: 6px;
877
1109
  background: var(--muted);
878
- color: var(--muted-foreground, var(--foreground));
1110
+ color: var(--foreground);
879
1111
  font-size: 12px;
880
1112
  line-height: 1;
881
- cursor: default;
1113
+ cursor: pointer;
1114
+
1115
+ &:hover,
1116
+ &.is-open {
1117
+ border-color: var(--brand, #4e5dff);
1118
+ }
882
1119
 
883
- i {
884
- font-size: 14px;
1120
+ svg {
885
1121
  opacity: 0.7;
886
1122
  }
887
1123
  }
888
1124
 
889
- .vm-color-picker__hex,
1125
+ .vm-color-picker__format-menu {
1126
+ position: absolute;
1127
+ left: 0;
1128
+ bottom: calc(100% + 4px);
1129
+ z-index: 2;
1130
+ min-width: 96px;
1131
+ padding: 4px;
1132
+ border: 1px solid var(--border);
1133
+ border-radius: 8px;
1134
+ background: var(--popover, var(--background, #fff));
1135
+ box-shadow:
1136
+ 0 4px 6px -1px rgb(0 0 0 / 10%),
1137
+ 0 10px 20px -4px rgb(0 0 0 / 12%);
1138
+ }
1139
+
1140
+ .vm-color-picker__format-item {
1141
+ display: flex;
1142
+ align-items: center;
1143
+ gap: 6px;
1144
+ width: 100%;
1145
+ height: 28px;
1146
+ padding: 0 8px;
1147
+ border: none;
1148
+ border-radius: 6px;
1149
+ background: transparent;
1150
+ color: var(--foreground);
1151
+ font-size: 12px;
1152
+ text-align: left;
1153
+ cursor: pointer;
1154
+
1155
+ &:hover {
1156
+ background: color-mix(in srgb, var(--brand, #4e5dff) 12%, transparent);
1157
+ }
1158
+
1159
+ &.is-active {
1160
+ background: var(--brand, #4e5dff);
1161
+ color: #fff;
1162
+ }
1163
+ }
1164
+
1165
+ .vm-color-picker__format-check {
1166
+ display: inline-flex;
1167
+ width: 14px;
1168
+ height: 14px;
1169
+ flex-shrink: 0;
1170
+ align-items: center;
1171
+ justify-content: center;
1172
+ }
1173
+
1174
+ .vm-color-picker__value,
890
1175
  .vm-color-picker__alpha-pct {
891
1176
  height: 28px;
892
1177
  padding: 0 8px;
@@ -903,7 +1188,7 @@ onUnmounted(() => {
903
1188
  }
904
1189
  }
905
1190
 
906
- .vm-color-picker__hex {
1191
+ .vm-color-picker__value {
907
1192
  flex: 1;
908
1193
  min-width: 0;
909
1194
  }