vome-core 0.1.25 → 0.1.27

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 _0x4ce1fa=_0x2abb;(function(_0x2d47bb,_0x23fffb){const _0x13567b=_0x2abb,_0x142a3a=_0x2d47bb();while(!![]){try{const _0x1f4e59=-parseInt(_0x13567b(0xcf))/0x1+-parseInt(_0x13567b(0xcc))/0x2*(parseInt(_0x13567b(0xde))/0x3)+-parseInt(_0x13567b(0xd4))/0x4*(-parseInt(_0x13567b(0xe8))/0x5)+parseInt(_0x13567b(0xd6))/0x6+-parseInt(_0x13567b(0xce))/0x7*(parseInt(_0x13567b(0xe7))/0x8)+parseInt(_0x13567b(0xdc))/0x9*(-parseInt(_0x13567b(0xe4))/0xa)+-parseInt(_0x13567b(0xd5))/0xb*(-parseInt(_0x13567b(0xd1))/0xc);if(_0x1f4e59===_0x23fffb)break;else _0x142a3a['push'](_0x142a3a['shift']());}catch(_0x2e9673){_0x142a3a['push'](_0x142a3a['shift']());}}}(_0x3942,0x6850c));const ORG=_0x4ce1fa(0xda);export function serviceUrlFromDocs(_0x151de4){const _0x3ead6a=_0x4ce1fa,_0x23aecd=new URL(_0x151de4),_0x326b33=_0x23aecd[_0x3ead6a(0xe5)][_0x3ead6a(0xe2)](/^www\./,'')[_0x3ead6a(0xe2)](/^service\./,'');return _0x23aecd['protocol']+'//service.'+_0x326b33;}export function seatApiBaseFromDocs(_0x51c72c){const _0x27216f=_0x4ce1fa;return serviceUrlFromDocs(_0x51c72c)+_0x27216f(0xdb);}function _0x2abb(_0x29b7e4,_0x57e7e1){_0x29b7e4=_0x29b7e4-0xcb;const _0x3942b0=_0x3942();let _0x2abb1c=_0x3942b0[_0x29b7e4];if(_0x2abb['cqlRSa']===undefined){var _0x3de77b=function(_0x21ce86){const _0x51cacd='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x151de4='',_0x23aecd='';for(let _0x326b33=0x0,_0x51c72c,_0x16d2e3,_0xc843f3=0x0;_0x16d2e3=_0x21ce86['charAt'](_0xc843f3++);~_0x16d2e3&&(_0x51c72c=_0x326b33%0x4?_0x51c72c*0x40+_0x16d2e3:_0x16d2e3,_0x326b33++%0x4)?_0x151de4+=String['fromCharCode'](0xff&_0x51c72c>>(-0x2*_0x326b33&0x6)):0x0){_0x16d2e3=_0x51cacd['indexOf'](_0x16d2e3);}for(let _0x35b1c7=0x0,_0xdb5a6b=_0x151de4['length'];_0x35b1c7<_0xdb5a6b;_0x35b1c7++){_0x23aecd+='%'+('00'+_0x151de4['charCodeAt'](_0x35b1c7)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x23aecd);};_0x2abb['SZsmxe']=_0x3de77b,_0x2abb['qBHrLr']={},_0x2abb['cqlRSa']=!![];}const _0x27a24c=_0x3942b0[0x0],_0x88488e=_0x29b7e4+_0x27a24c,_0x2403d4=_0x2abb['qBHrLr'][_0x88488e];return!_0x2403d4?(_0x2abb1c=_0x2abb['SZsmxe'](_0x2abb1c),_0x2abb['qBHrLr'][_0x88488e]=_0x2abb1c):_0x2abb1c=_0x2403d4,_0x2abb1c;}export function licenseIssueUrlFromDocs(_0x16d2e3){const _0x185257=_0x4ce1fa;return serviceUrlFromDocs(_0x16d2e3)+_0x185257(0xd9);}function giteeArchive(_0xc843f3){const _0x1946cc=_0x4ce1fa;return _0x1946cc(0xd7)+ORG+'/'+_0xc843f3+_0x1946cc(0xd2);}function githubArchive(_0x35b1c7){const _0x39cbae=_0x4ce1fa;return _0x39cbae(0xdd)+ORG+'/'+_0x35b1c7+_0x39cbae(0xdf);}function giteeRepo(_0xdb5a6b){const _0x4c5fc6=_0x4ce1fa;return _0x4c5fc6(0xd7)+ORG+'/'+_0xdb5a6b;}function githubRepo(_0x29838a){const _0x3f5578=_0x4ce1fa;return _0x3f5578(0xdd)+ORG+'/'+_0x29838a;}const docsUrl='https://vomekj.com';export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+_0x4ce1fa(0xd0),'mirrors':{'prefer':_0x4ce1fa(0xd3),'giteeOrg':_0x4ce1fa(0xd7)+ORG,'githubOrg':'https://github.com/'+ORG},'scaffolds':{'full':{'key':'full','title':'前端\x20+\x20后端','desc':'完整业务模块脚手架(.vome:含\x20server\x20与\x20web),本地开发打包后发布。','repo':'vome-plugin-full','repoUrl':giteeRepo('vome-plugin-full'),'githubRepoUrl':githubRepo(_0x4ce1fa(0xe6)),'downloadUrl':giteeArchive(_0x4ce1fa(0xe6)),'githubDownloadUrl':githubArchive('vome-plugin-full')},'backend':{'key':_0x4ce1fa(0xe3),'title':_0x4ce1fa(0xcd),'desc':'钩子\x20/\x20服务端插件脚手架(.vome:仅\x20server),适合上传、短信、支付等能力扩展。','repo':'vome-plugin-service','repoUrl':giteeRepo('vome-plugin-service'),'githubRepoUrl':githubRepo(_0x4ce1fa(0xcb)),'downloadUrl':giteeArchive(_0x4ce1fa(0xcb)),'githubDownloadUrl':githubArchive(_0x4ce1fa(0xcb))},'frontend':{'key':'frontend','title':_0x4ce1fa(0xe1),'desc':_0x4ce1fa(0xd8),'repo':_0x4ce1fa(0xe0),'repoUrl':giteeRepo(_0x4ce1fa(0xe0)),'githubRepoUrl':githubRepo(_0x4ce1fa(0xe0)),'downloadUrl':giteeArchive('vome-plugin-front'),'githubDownloadUrl':githubArchive(_0x4ce1fa(0xe0))}}};function _0x3942(){const _0x1dcafd=['mJrxsgPzCMe','mtm5mJqYnxfqD3PYzq','DM9Tzs1WBhvNAw4TC2vYDMLJzq','mtG4nK9ftKTrtG','57QV5zco56UV','mtqZmty0vhnfv2Hi','nZG4mtyWvxHbweDT','l3bSDwDPBNmVDxbSB2fK','otiWmtq4zuDsvvju','l3jLCg9ZAxrVCNKVyxjJAgL2zs9Tyxn0zxiUEMLW','z2L0zwu','oefbEMnKuG','mtu0zhHeChLr','mZK2mJiZmLPosKfWqW','Ahr0Chm6lY9NAxrLzs5JB20V','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','l2fWCc9WBhvNAw5tDg9Yzs93ywXSzxqVBgLJzw5Zzs9PC3n1zq','DM9TzwTQ','l2fWCc9WBhvNAw5tDg9Yzs9Zzwf0','mJm0otLHC1bQCKW','Ahr0Chm6lY9NAxrODwiUy29TlW','otC1tg14zeXS','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','DM9Tzs1WBhvNAw4TzNjVBNq','57QV5yMn56UV','CMvWBgfJzq','yMfJA2vUza','mJCXmgjbsxr4va','Ag9ZDg5HBwu','DM9Tzs1WBhvNAw4TzNvSBa'];_0x3942=function(){return _0x1dcafd;};return _0x3942();}
1
+ const _0x245ea6=_0x5738;(function(_0x217bd0,_0x3783bd){const _0x8ef90f=_0x5738,_0x24f6a2=_0x217bd0();while(!![]){try{const _0x19b1e6=parseInt(_0x8ef90f(0x178))/0x1+-parseInt(_0x8ef90f(0x193))/0x2+parseInt(_0x8ef90f(0x189))/0x3+-parseInt(_0x8ef90f(0x18f))/0x4+parseInt(_0x8ef90f(0x191))/0x5*(-parseInt(_0x8ef90f(0x183))/0x6)+-parseInt(_0x8ef90f(0x190))/0x7*(parseInt(_0x8ef90f(0x17d))/0x8)+parseInt(_0x8ef90f(0x186))/0x9*(parseInt(_0x8ef90f(0x181))/0xa);if(_0x19b1e6===_0x3783bd)break;else _0x24f6a2['push'](_0x24f6a2['shift']());}catch(_0x318829){_0x24f6a2['push'](_0x24f6a2['shift']());}}}(_0x1ba4,0xadcb7));const ORG=_0x245ea6(0x17e);export function serviceUrlFromDocs(_0x518aec){const _0x368b3f=_0x245ea6,_0x12bba5=new URL(_0x518aec),_0x515ef0=_0x12bba5['hostname'][_0x368b3f(0x18c)](/^www\./,'')['replace'](/^service\./,'');return _0x12bba5[_0x368b3f(0x182)]+'//service.'+_0x515ef0;}export function seatApiBaseFromDocs(_0x167144){const _0xd6b169=_0x245ea6;return serviceUrlFromDocs(_0x167144)+_0xd6b169(0x17a);}export function licenseIssueUrlFromDocs(_0x58196e){const _0x84dd57=_0x245ea6;return serviceUrlFromDocs(_0x58196e)+_0x84dd57(0x180);}function _0x1ba4(){const _0x4d745a=['mtGZmJK5mhHjEMPQDa','ChjVDg9JB2W','nte3og1TtwHdzG','5yMn56UVicSG5zco56UV','DM9Tzs1WBhvNAw4TC2vYDMLJzq','mta4z1fTwgn4','z2L0zwu','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','mZyYnJG3neros2Dctq','zNvSBa','DM9Tzs1WBhvNAw4TzNvSBa','CMvWBgfJzq','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','57QV5yMn56UV','ndyXnJe2ohH4ywDSDG','n0XvsfjNwG','nJq3mgnMwNrvwG','Ahr0Chm6lY9NAxrODwiUy29TlW','mJuYota4mNzTs0rbrW','6zkP5A2qic8G5PYn5yQH56UV5O+s5lU26isA5OMl5P6277YilNzVBwxVVjRKU4uGC2vYDMvY77Yj77Ym6ycc5zci5lIk5lYG44cb55+T5l+H44cb5PsV5lUy562j6io95yQB5OMP5Bgv44cc','Ahr0Chm6lY9NAxrLzs5JB20V','DM9Tzs1WBhvNAw4TzNjVBNq','mteYnZC3m0zey2L4sG','yMfJA2vUza','l2fWCc9WBhvNAw5tDg9Yzs9Zzwf0','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','zNjVBNrLBMq','mJmXmZiWohLmDhH6uq','DM9TzwTQ','l3bSDwDPBNmVDxbSB2fK','l2fWCc9WBhvNAw5tDg9Yzs93ywXSzxqVBgLJzw5Zzs9PC3n1zq'];_0x1ba4=function(){return _0x4d745a;};return _0x1ba4();}function giteeArchive(_0x31818e){const _0x3f07f3=_0x245ea6;return _0x3f07f3(0x176)+ORG+'/'+_0x31818e+'/repository/archive/master.zip';}function githubArchive(_0x37a8f5){const _0x51d5ee=_0x245ea6;return _0x51d5ee(0x192)+ORG+'/'+_0x37a8f5+_0x51d5ee(0x188);}function giteeRepo(_0x649b87){return'https://gitee.com/'+ORG+'/'+_0x649b87;}function githubRepo(_0x39d849){return'https://github.com/'+ORG+'/'+_0x39d849;}function _0x5738(_0x3d205a,_0x49a45f){_0x3d205a=_0x3d205a-0x175;const _0x1ba4c1=_0x1ba4();let _0x573808=_0x1ba4c1[_0x3d205a];if(_0x5738['BzNZAs']===undefined){var _0x32c628=function(_0x3248e0){const _0x9056ea='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x518aec='',_0x12bba5='';for(let _0x515ef0=0x0,_0x167144,_0x58196e,_0x31818e=0x0;_0x58196e=_0x3248e0['charAt'](_0x31818e++);~_0x58196e&&(_0x167144=_0x515ef0%0x4?_0x167144*0x40+_0x58196e:_0x58196e,_0x515ef0++%0x4)?_0x518aec+=String['fromCharCode'](0xff&_0x167144>>(-0x2*_0x515ef0&0x6)):0x0){_0x58196e=_0x9056ea['indexOf'](_0x58196e);}for(let _0x37a8f5=0x0,_0x649b87=_0x518aec['length'];_0x37a8f5<_0x649b87;_0x37a8f5++){_0x12bba5+='%'+('00'+_0x518aec['charCodeAt'](_0x37a8f5)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x12bba5);};_0x5738['UlgIzL']=_0x32c628,_0x5738['zMFEqs']={},_0x5738['BzNZAs']=!![];}const _0x956bbb=_0x1ba4c1[0x0],_0x506e04=_0x3d205a+_0x956bbb,_0x1be382=_0x5738['zMFEqs'][_0x506e04];return!_0x1be382?(_0x573808=_0x5738['UlgIzL'](_0x573808),_0x5738['zMFEqs'][_0x506e04]=_0x573808):_0x573808=_0x1be382,_0x573808;}const docsUrl='https://vomekj.com';export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+_0x245ea6(0x17f),'mirrors':{'prefer':_0x245ea6(0x187),'giteeOrg':_0x245ea6(0x176)+ORG,'githubOrg':_0x245ea6(0x192)+ORG},'scaffolds':{'full':{'key':_0x245ea6(0x18a),'title':_0x245ea6(0x184),'desc':_0x245ea6(0x17b),'repo':'vome-plugin-full','repoUrl':giteeRepo(_0x245ea6(0x18b)),'githubRepoUrl':githubRepo(_0x245ea6(0x18b)),'downloadUrl':giteeArchive('vome-plugin-full'),'githubDownloadUrl':githubArchive(_0x245ea6(0x18b))},'backend':{'key':_0x245ea6(0x179),'title':'纯后端','desc':_0x245ea6(0x175),'repo':_0x245ea6(0x185),'repoUrl':giteeRepo(_0x245ea6(0x185)),'githubRepoUrl':githubRepo('vome-plugin-service'),'downloadUrl':giteeArchive(_0x245ea6(0x185)),'githubDownloadUrl':githubArchive(_0x245ea6(0x185))},'frontend':{'key':_0x245ea6(0x17c),'title':_0x245ea6(0x18e),'desc':_0x245ea6(0x18d),'repo':'vome-plugin-front','repoUrl':giteeRepo(_0x245ea6(0x177)),'githubRepoUrl':githubRepo(_0x245ea6(0x177)),'downloadUrl':giteeArchive(_0x245ea6(0x177)),'githubDownloadUrl':githubArchive(_0x245ea6(0x177))}}};
@@ -21,15 +21,16 @@
21
21
  <input
22
22
  type="text"
23
23
  class="vm-color-picker__input"
24
- :value="inlineHex"
24
+ :value="inlineDisplay"
25
25
  :placeholder="placeholderText"
26
26
  :disabled="disabled"
27
27
  spellcheck="false"
28
28
  autocomplete="off"
29
+ :title="isThemeBound ? themeBoundLabel : undefined"
29
30
  @input="onInlineInput"
30
31
  @focus="openPanel"
31
32
  @blur="onInlineBlur"
32
- @keydown.esc.stop="closePanel"
33
+ @keydown.esc.stop="onEscEnd"
33
34
  />
34
35
 
35
36
  <Teleport to="body">
@@ -47,6 +48,69 @@
47
48
  @pointerdown.stop
48
49
  @mousedown.stop
49
50
  >
51
+ <div class="vm-color-picker__toolbar">
52
+ <div
53
+ v-if="hasThemeOptions"
54
+ class="vm-color-picker__modes"
55
+ role="tablist"
56
+ >
57
+ <button
58
+ type="button"
59
+ class="vm-color-picker__mode"
60
+ :class="{ 'is-on': panelMode === 'theme' }"
61
+ role="tab"
62
+ @click="panelMode = 'theme'"
63
+ >
64
+ 主题
65
+ </button>
66
+ <button
67
+ type="button"
68
+ class="vm-color-picker__mode"
69
+ :class="{ 'is-on': panelMode === 'picker' }"
70
+ role="tab"
71
+ @click="panelMode = 'picker'"
72
+ >
73
+ 取色器
74
+ </button>
75
+ </div>
76
+ <span v-else class="vm-color-picker__toolbar-spacer" />
77
+ <button
78
+ type="button"
79
+ class="vm-color-picker__close"
80
+ title="关闭"
81
+ aria-label="关闭"
82
+ @click="closePanel"
83
+ >
84
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
85
+ <path
86
+ fill="currentColor"
87
+ d="M6.4 19 5 17.6 10.6 12 5 6.4 6.4 5 12 10.6 17.6 5 19 6.4 13.4 12 19 17.6 17.6 19 12 13.4 6.4 19Z"
88
+ />
89
+ </svg>
90
+ </button>
91
+ </div>
92
+
93
+ <div v-if="panelMode === 'theme' && hasThemeOptions" class="vm-color-picker__theme">
94
+ <button
95
+ v-for="opt in themeOptions"
96
+ :key="opt.key"
97
+ type="button"
98
+ class="vm-color-picker__theme-item"
99
+ :class="{ 'is-on': themeBoundKey === opt.key }"
100
+ @click="pickTheme(opt.key)"
101
+ >
102
+ <span
103
+ class="vm-color-picker__theme-swatch"
104
+ :style="{ background: opt.value }"
105
+ />
106
+ <span class="vm-color-picker__theme-meta">
107
+ <span class="vm-color-picker__theme-label">{{ opt.label }}</span>
108
+ <span class="vm-color-picker__theme-key">{{ opt.key }}</span>
109
+ </span>
110
+ </button>
111
+ </div>
112
+
113
+ <template v-else>
50
114
  <div
51
115
  ref="svRef"
52
116
  class="vm-color-picker__sv"
@@ -154,7 +218,7 @@
154
218
  autocomplete="off"
155
219
  @input="onPanelValueInput"
156
220
  @blur="onPanelValueBlur"
157
- @keydown.esc.stop="closePanel"
221
+ @keydown.esc.stop="onEscEnd"
158
222
  />
159
223
  <input
160
224
  type="text"
@@ -164,9 +228,10 @@
164
228
  autocomplete="off"
165
229
  @input="onAlphaPctInput"
166
230
  @blur="onAlphaPctBlur"
167
- @keydown.esc.stop="closePanel"
231
+ @keydown.esc.stop="onEscEnd"
168
232
  />
169
233
  </div>
234
+ </template>
170
235
  </div>
171
236
  </FocusScope>
172
237
  </Teleport>
@@ -179,16 +244,26 @@ import { FocusScope } from 'reka-ui'
179
244
 
180
245
  defineOptions({ name: 'vm-color-picker' })
181
246
 
247
+ /** 主题色选项(有则显示「主题 / 取色器」) */
248
+ export type VmColorThemeOption = {
249
+ key: string
250
+ label: string
251
+ /** 当前模式下的预览色 */
252
+ value: string
253
+ }
254
+
182
255
  const props = withDefaults(
183
256
  defineProps<{
184
257
  modelValue?: string | null
185
258
  disabled?: boolean
186
259
  placeholder?: string
187
260
  size?: 'default' | 'sm'
261
+ themeOptions?: VmColorThemeOption[]
188
262
  }>(),
189
263
  {
190
264
  placeholder: '#000000',
191
265
  size: 'default',
266
+ themeOptions: () => [],
192
267
  },
193
268
  )
194
269
 
@@ -201,6 +276,19 @@ type Rgba = { r: number; g: number; b: number; a: number }
201
276
  type Hsv = { h: number; s: number; v: number }
202
277
  type ColorFormat = 'hex' | 'rgb' | 'css' | 'hsl' | 'hsb'
203
278
 
279
+ function parseCssVarRef(raw: string): string | null {
280
+ const s = String(raw || '').trim()
281
+ const m = s.match(/^var\(\s*--([a-zA-Z0-9_-]+)\s*(?:,[^)]*)?\)$/i)
282
+ return m ? m[1]! : null
283
+ }
284
+
285
+ function toCssVarRef(key: string): string {
286
+ const k = String(key || '')
287
+ .trim()
288
+ .replace(/^--/, '')
289
+ return k ? `var(--${k})` : ''
290
+ }
291
+
204
292
  const FORMAT_OPTS: { id: ColorFormat; label: string }[] = [
205
293
  { id: 'hex', label: 'Hex' },
206
294
  { id: 'rgb', label: 'RGB' },
@@ -223,6 +311,8 @@ const suppressOpen = ref(false)
223
311
  const panelStyle = ref<Record<string, string>>({})
224
312
  const formatOpen = ref(false)
225
313
  const colorFormat = ref<ColorFormat>('hex')
314
+ /** 面板:主题变量 | 取色器 */
315
+ const panelMode = ref<'theme' | 'picker'>('picker')
226
316
 
227
317
  const hue = ref(0)
228
318
  const sat = ref(0)
@@ -236,6 +326,38 @@ const alphaPctText = ref('100%')
236
326
 
237
327
  const hasEyeDropper = ref(false)
238
328
 
329
+ const themeOptions = computed(() => props.themeOptions || [])
330
+ const hasThemeOptions = computed(() => themeOptions.value.length > 0)
331
+ const themeBoundKey = computed(() => parseCssVarRef(props.modelValue ?? ''))
332
+ const isThemeBound = computed(() => Boolean(themeBoundKey.value))
333
+ const themeBoundOpt = computed(() => {
334
+ const k = themeBoundKey.value
335
+ if (!k) return null
336
+ return themeOptions.value.find((o) => o.key === k) || null
337
+ })
338
+ const themeBoundLabel = computed(() => {
339
+ const opt = themeBoundOpt.value
340
+ if (opt) return opt.label
341
+ const k = themeBoundKey.value
342
+ return k ? k : ''
343
+ })
344
+ const inlineDisplay = computed(() => {
345
+ if (isThemeBound.value) {
346
+ return String(props.modelValue || '').trim() || themeBoundLabel.value
347
+ }
348
+ return inlineHex.value
349
+ })
350
+
351
+ /** 行内:主题变量 / mustache,不当作 hex 强行加 # */
352
+ function isInlineExpr(raw: string): boolean {
353
+ const s = String(raw || '').trim()
354
+ if (!s) return false
355
+ if (/\{\{[\s\S]*?\}\}/.test(s)) return true
356
+ if (parseCssVarRef(s)) return true
357
+ if (/^var\s*\(/i.test(s)) return true
358
+ return false
359
+ }
360
+
239
361
  const placeholderText = computed(() => {
240
362
  const p = props.placeholder || '#000000'
241
363
  return p.startsWith('#') ? p.slice(1) : p
@@ -520,13 +642,33 @@ function formatPanelValue(): string {
520
642
  }
521
643
 
522
644
  const cssColor = computed(() => {
523
- const parsed = parseColor(props.modelValue ?? '')
524
- if (!parsed && !(props.modelValue ?? '').trim()) return 'transparent'
645
+ const themePreview = themeBoundOpt.value?.value
646
+ if (themePreview) {
647
+ const parsed = parseColor(themePreview)
648
+ if (parsed) {
649
+ const { r, g, b, a } = parsed
650
+ return a < 1 ? `rgba(${r},${g},${b},${a})` : `rgb(${r},${g},${b})`
651
+ }
652
+ return String(themePreview)
653
+ }
654
+ if (themeBoundKey.value) {
655
+ return `var(--${themeBoundKey.value})`
656
+ }
657
+ const raw = props.modelValue ?? ''
658
+ const parsed = parseColor(raw)
659
+ if (!parsed && !String(raw).trim()) return 'transparent'
525
660
  if (!parsed) return 'transparent'
526
661
  const { r, g, b, a } = parsed
527
662
  return a < 1 ? `rgba(${r},${g},${b},${a})` : `rgb(${r},${g},${b})`
528
663
  })
529
664
 
665
+ function pickTheme(key: string) {
666
+ const ref = toCssVarRef(key)
667
+ if (!ref) return
668
+ commit(ref)
669
+ closePanel()
670
+ }
671
+
530
672
  const svBg = computed(() => {
531
673
  const { r, g, b } = hsvToRgb(hue.value, 1, 1)
532
674
  return `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, rgb(${Math.round(r)},${Math.round(g)},${Math.round(b)}))`
@@ -563,6 +705,26 @@ function applyRgba(c: Rgba, emitValue = true) {
563
705
 
564
706
  function loadFromModel(raw: string | null | undefined) {
565
707
  const s = raw ?? ''
708
+ const varKey = parseCssVarRef(s)
709
+ if (varKey) {
710
+ const opt = themeOptions.value.find((o) => o.key === varKey)
711
+ const preview = opt?.value || ''
712
+ if (preview) {
713
+ const parsed = parseColor(preview)
714
+ if (parsed) {
715
+ const hsv = rgbToHsv(parsed.r, parsed.g, parsed.b)
716
+ hue.value = hsv.h
717
+ sat.value = hsv.s
718
+ val.value = hsv.v
719
+ alpha.value = parsed.a
720
+ syncTextsFromState()
721
+ return
722
+ }
723
+ }
724
+ inlineHex.value = varKey
725
+ panelValue.value = varKey
726
+ return
727
+ }
566
728
  if (!s.trim()) {
567
729
  hue.value = 0
568
730
  sat.value = 0
@@ -610,9 +772,15 @@ function commitFromState() {
610
772
  }
611
773
 
612
774
  function onInlineInput(e: Event) {
613
- const v = (e.target as HTMLInputElement).value
614
- inlineHex.value = v.replace(/^#/, '')
615
- const parsed = parseColor(inlineHex.value)
775
+ const rawInput = (e.target as HTMLInputElement).value
776
+ if (isInlineExpr(rawInput)) {
777
+ inlineHex.value = rawInput
778
+ commit(rawInput.trim())
779
+ return
780
+ }
781
+ const v = rawInput.replace(/^#/, '')
782
+ inlineHex.value = v
783
+ const parsed = parseColor(v)
616
784
  if (parsed) {
617
785
  parsed.a = alpha.value
618
786
  const hsv = rgbToHsv(parsed.r, parsed.g, parsed.b)
@@ -621,26 +789,44 @@ function onInlineInput(e: Event) {
621
789
  val.value = hsv.v
622
790
  panelValue.value = formatPanelValue()
623
791
  commit(currentHex())
624
- } else {
625
- const raw = inlineHex.value.trim()
626
- commit(raw ? (raw.startsWith('#') ? raw : `#${raw}`) : '')
792
+ return
793
+ }
794
+ const raw = v.trim()
795
+ if (!raw) {
796
+ commit('')
797
+ return
798
+ }
799
+ if (/^[0-9a-fA-F]{1,8}$/.test(raw)) {
800
+ commit(`#${raw}`)
801
+ return
627
802
  }
803
+ commit(raw)
628
804
  }
629
805
 
630
806
  function onInlineBlur() {
631
807
  const t = inlineHex.value.trim()
632
808
  if (!t) {
633
809
  commit('')
634
- syncTextsFromState()
810
+ hue.value = 0
811
+ sat.value = 0
812
+ val.value = 0
813
+ alpha.value = 1
814
+ inlineHex.value = ''
815
+ panelValue.value = ''
816
+ alphaPctText.value = '100%'
817
+ return
818
+ }
819
+ if (isInlineExpr(t)) {
820
+ commit(t)
635
821
  return
636
822
  }
637
823
  const parsed = parseColor(t)
638
824
  if (parsed) {
639
825
  parsed.a = alpha.value
640
826
  applyRgba(parsed, true)
641
- } else {
642
- loadFromModel(props.modelValue)
827
+ return
643
828
  }
829
+ loadFromModel(props.modelValue)
644
830
  }
645
831
 
646
832
  function onPanelValueInput(e: Event) {
@@ -801,6 +987,13 @@ async function openPanel() {
801
987
  if (props.disabled || suppressOpen.value) return
802
988
  loadFromModel(props.modelValue)
803
989
  formatOpen.value = false
990
+ if (hasThemeOptions.value) {
991
+ const bound = parseCssVarRef(props.modelValue ?? '')
992
+ const empty = !(props.modelValue ?? '').trim()
993
+ panelMode.value = bound || empty ? 'theme' : 'picker'
994
+ } else {
995
+ panelMode.value = 'picker'
996
+ }
804
997
  open.value = true
805
998
  await nextTick()
806
999
  syncPanelPos()
@@ -822,6 +1015,13 @@ function closePanel() {
822
1015
  })
823
1016
  }
824
1017
 
1018
+ /** Esc:收起面板并失焦 */
1019
+ function onEscEnd(e: KeyboardEvent) {
1020
+ closePanel()
1021
+ const el = e.target
1022
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) el.blur()
1023
+ }
1024
+
825
1025
  async function toggle() {
826
1026
  if (props.disabled) return
827
1027
  if (open.value) {
@@ -964,6 +1164,146 @@ onUnmounted(() => {
964
1164
  overflow: visible;
965
1165
  }
966
1166
 
1167
+ .vm-color-picker__toolbar {
1168
+ display: flex;
1169
+ align-items: center;
1170
+ gap: 8px;
1171
+ margin-bottom: 10px;
1172
+ }
1173
+
1174
+ .vm-color-picker__toolbar-spacer {
1175
+ flex: 1;
1176
+ min-width: 0;
1177
+ }
1178
+
1179
+ .vm-color-picker__close {
1180
+ display: inline-flex;
1181
+ flex-shrink: 0;
1182
+ align-items: center;
1183
+ justify-content: center;
1184
+ width: 28px;
1185
+ height: 28px;
1186
+ margin: 0;
1187
+ padding: 0;
1188
+ border: 0;
1189
+ border-radius: 6px;
1190
+ background: transparent;
1191
+ color: var(--muted-foreground, #9aa0b0);
1192
+ cursor: pointer;
1193
+
1194
+ &:hover {
1195
+ background: var(--muted, #f4f6fc);
1196
+ color: var(--foreground);
1197
+ }
1198
+ }
1199
+
1200
+ .vm-color-picker__modes {
1201
+ display: grid;
1202
+ flex: 1;
1203
+ min-width: 0;
1204
+ grid-template-columns: 1fr 1fr;
1205
+ gap: 4px;
1206
+ margin: 0;
1207
+ padding: 3px;
1208
+ border-radius: 8px;
1209
+ background: var(--muted, #f4f6fc);
1210
+ }
1211
+
1212
+ .vm-color-picker__mode {
1213
+ height: 28px;
1214
+ border: 0;
1215
+ border-radius: 6px;
1216
+ background: transparent;
1217
+ color: var(--muted-foreground, #9aa0b0);
1218
+ font-size: 12px;
1219
+ font-weight: 600;
1220
+ cursor: pointer;
1221
+
1222
+ &.is-on {
1223
+ background: var(--card, #fff);
1224
+ color: var(--foreground);
1225
+ box-shadow: 0 1px 2px rgb(0 0 0 / 6%);
1226
+ }
1227
+ }
1228
+
1229
+ .vm-color-picker__theme {
1230
+ display: flex;
1231
+ flex-direction: column;
1232
+ gap: 4px;
1233
+ max-height: 280px;
1234
+ overflow: auto;
1235
+ margin: 0 -4px;
1236
+ padding: 0 4px;
1237
+ }
1238
+
1239
+ .vm-color-picker__theme-item {
1240
+ display: flex;
1241
+ align-items: center;
1242
+ gap: 10px;
1243
+ width: 100%;
1244
+ margin: 0;
1245
+ padding: 8px;
1246
+ border: 1px solid transparent;
1247
+ border-radius: 8px;
1248
+ background: transparent;
1249
+ text-align: left;
1250
+ cursor: pointer;
1251
+ color: inherit;
1252
+
1253
+ &:hover {
1254
+ background: color-mix(in srgb, var(--brand, #4e5dff) 8%, transparent);
1255
+ }
1256
+
1257
+ &.is-on {
1258
+ border-color: color-mix(in srgb, var(--brand, #4e5dff) 45%, transparent);
1259
+ background: color-mix(in srgb, var(--brand, #4e5dff) 10%, transparent);
1260
+ }
1261
+ }
1262
+
1263
+ .vm-color-picker__theme-swatch {
1264
+ flex-shrink: 0;
1265
+ width: 22px;
1266
+ height: 22px;
1267
+ border-radius: 6px;
1268
+ border: 1px solid var(--border, #e8ebf5);
1269
+ background-image:
1270
+ linear-gradient(45deg, #ccc 25%, transparent 25%),
1271
+ linear-gradient(-45deg, #ccc 25%, transparent 25%),
1272
+ linear-gradient(45deg, transparent 75%, #ccc 75%),
1273
+ linear-gradient(-45deg, transparent 75%, #ccc 75%);
1274
+ background-size: 8px 8px;
1275
+ background-position:
1276
+ 0 0,
1277
+ 0 4px,
1278
+ 4px -4px,
1279
+ -4px 0;
1280
+ }
1281
+
1282
+ .vm-color-picker__theme-meta {
1283
+ min-width: 0;
1284
+ display: flex;
1285
+ flex-direction: column;
1286
+ gap: 2px;
1287
+ }
1288
+
1289
+ .vm-color-picker__theme-label {
1290
+ font-size: 13px;
1291
+ font-weight: 600;
1292
+ line-height: 1.2;
1293
+ overflow: hidden;
1294
+ text-overflow: ellipsis;
1295
+ white-space: nowrap;
1296
+ }
1297
+
1298
+ .vm-color-picker__theme-key {
1299
+ font-size: 11px;
1300
+ color: var(--muted-foreground, #9aa0b0);
1301
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1302
+ overflow: hidden;
1303
+ text-overflow: ellipsis;
1304
+ white-space: nowrap;
1305
+ }
1306
+
967
1307
  .vm-color-picker__sv {
968
1308
  position: relative;
969
1309
  width: 100%;