@holyer-lib/ui 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui.cjs.js CHANGED
@@ -14,6 +14,957 @@ function __$styleInject(css) {
14
14
  return css;
15
15
  }
16
16
 
17
+ /**
18
+ * 类型判断工具
19
+ */
20
+
21
+ /**
22
+ * 将数值或字符串转换为合法的 CSS 长度值(用于 style 绑定)
23
+ * - 若为 number,自动追加 'px' 单位
24
+ * - 若为 string,原样返回(假定用户已提供合法 CSS 长度值)
25
+ * @param {number|string} value - 输入值(如 100, '100%', '50vh', 'auto')
26
+ * @returns {string} 合法的 CSS 长度字符串 默认值为 0
27
+ * @example
28
+ * formatSize(100) // '100px'
29
+ * formatSize('100%') // '100%'
30
+ * formatSize('50vh') // '50vh'
31
+ * formatSize(null) // 0
32
+ * formatSize(undefined) // 0
33
+ */
34
+ function formatSize$1(value) {
35
+ if (typeof value === 'number') {
36
+ return `${value}px`;
37
+ }
38
+ if (typeof value === 'string') {
39
+ if (value.trim() === '') {
40
+ return 0;
41
+ }
42
+ return value;
43
+ }
44
+ return 0;
45
+ }
46
+
47
+ //
48
+
49
+ const validateSize = val => {
50
+ if (typeof val === 'number') {
51
+ return val >= 0;
52
+ }
53
+ return true;
54
+ };
55
+
56
+ var script$3 = {
57
+ name: 'HiExpandPanel',
58
+ model: {
59
+ prop: 'expanded',
60
+ event: 'update:expanded'
61
+ },
62
+ props: {
63
+ // 支持受控和非受控两种模式
64
+ expanded: {
65
+ type: Boolean,
66
+ default: undefined
67
+ },
68
+
69
+ placement: {
70
+ type: String,
71
+ default: 'right',
72
+ validator: v => ['left', 'right', 'top', 'bottom'].includes(v)
73
+ },
74
+
75
+ size: {
76
+ type: [Number, String],
77
+ default: 280,
78
+ validator: validateSize
79
+ },
80
+
81
+ minSize: {
82
+ type: [Number, String],
83
+ default: 240,
84
+ validator: validateSize
85
+ },
86
+
87
+ maxSize: {
88
+ type: [Number, String],
89
+ default: 480,
90
+ validator: validateSize
91
+ },
92
+
93
+ collapsedSize: {
94
+ type: [String, Number],
95
+ default: 24,
96
+ validator: validateSize
97
+ },
98
+
99
+ draggable: {
100
+ type: Boolean,
101
+ default: true
102
+ },
103
+
104
+ showTrigger: {
105
+ type: Boolean,
106
+ default: true
107
+ },
108
+
109
+ cacheKey: {
110
+ type: String,
111
+ default: ''
112
+ },
113
+
114
+ cacheVersion: {
115
+ type: String,
116
+ default: ''
117
+ },
118
+
119
+ draggingBgColor: {
120
+ type: String,
121
+ default: 'var(--td-gray-color-6)'
122
+ }
123
+ },
124
+ data() {
125
+ let cachedData = {};
126
+ if (this.cacheKey) {
127
+ try {
128
+ const stored = localStorage.getItem(`${this.cacheKey}${this.cacheVersion}`);
129
+ if (stored) {
130
+ cachedData = JSON.parse(stored);
131
+ }
132
+ } catch (e) {
133
+ // eslint-disable-next-line no-console
134
+ console.warn(`HiExpandPanel: Failed to parse cache for key "${this.cacheKey}"`, e);
135
+ cachedData = {};
136
+ }
137
+ }
138
+
139
+ // 核心逻辑:有外部控制就用外部控制,否则用缓存
140
+ const useExternal = this.expanded !== undefined;
141
+ const initialExpanded = useExternal
142
+ ? this.expanded
143
+ : cachedData.cachedExpanded !== undefined
144
+ ? cachedData.cachedExpanded
145
+ : true;
146
+ const initialSize = useExternal
147
+ ? this.size
148
+ : cachedData.cachedSize !== undefined
149
+ ? cachedData.cachedSize
150
+ : this.size;
151
+
152
+ return {
153
+ innerExpanded: initialExpanded,
154
+ clientSize: initialSize,
155
+ isDragging: false,
156
+ startX: 0,
157
+ startY: 0,
158
+ startWidth: 0,
159
+ startHeight: 0,
160
+ hasExternalControl: useExternal
161
+ };
162
+ },
163
+ computed: {
164
+ isHorizontal() {
165
+ return ['left', 'right'].includes(this.placement);
166
+ },
167
+
168
+ // 根据展开状态和方向动态计算面板尺寸限制
169
+ getContainerStyles() {
170
+ // 如果未展开,直接返回空对象,不设置min/max尺寸
171
+ if (!this.innerExpanded) return {};
172
+ // 展开时根据方向设置对应的min/max尺寸
173
+ return this.isHorizontal
174
+ ? {
175
+ minWidth: formatSize$1(this.minSize),
176
+ maxWidth: formatSize$1(this.maxSize)
177
+ }
178
+ : {
179
+ minHeight: formatSize$1(this.minSize),
180
+ maxHeight: formatSize$1(this.maxSize)
181
+ };
182
+ },
183
+ panelStyle() {
184
+ const getWidth = () => {
185
+ if (this.isHorizontal) {
186
+ return this.innerExpanded ? formatSize$1(this.clientSize) : formatSize$1(this.collapsedSize) || 0;
187
+ }
188
+ return '100%';
189
+ };
190
+
191
+ const getHeight = () => {
192
+ if (this.isHorizontal) {
193
+ return '100%';
194
+ }
195
+ return this.innerExpanded ? formatSize$1(this.clientSize) : formatSize$1(this.collapsedSize) || 0;
196
+ };
197
+
198
+ return {
199
+ width: getWidth(),
200
+ height: getHeight(),
201
+ ...this.getContainerStyles,
202
+ transition: this.isDragging ? 'none' : 'flex 0.3s ease',
203
+ '--control-draggable-cursor': this.isHorizontal ? 'col-resize' : 'row-resize',
204
+ '--control-dragging-bg-color': this.draggingBgColor
205
+ };
206
+ }
207
+ },
208
+ watch: {
209
+ expanded(newVal) {
210
+ // 只有在组件是受控时才更新状态
211
+ if (this.hasExternalControl) {
212
+ if (newVal !== this.innerExpanded) {
213
+ this.innerExpanded = newVal;
214
+ }
215
+ }
216
+ },
217
+
218
+ innerExpanded(newVal) {
219
+ this.$emit('update:expanded', newVal);
220
+ this.$emit('expand-change', newVal);
221
+ if (this.cacheKey) {
222
+ this.handleSaveCache();
223
+ }
224
+ }
225
+ },
226
+ methods: {
227
+ handleToggle() {
228
+ this.innerExpanded = !this.innerExpanded;
229
+ },
230
+
231
+ handleSaveCache() {
232
+ if (this.cacheKey) {
233
+ localStorage.setItem(
234
+ `${this.cacheKey}${this.cacheVersion}`,
235
+ JSON.stringify({
236
+ cachedSize: this.clientSize,
237
+ cachedExpanded: this.innerExpanded
238
+ })
239
+ );
240
+ }
241
+ },
242
+
243
+ handleDragMousedown(e) {
244
+ if (!this.draggable) return;
245
+ e.preventDefault();
246
+ this.isDragging = true;
247
+ this.startX = e.clientX;
248
+ this.startY = e.clientY;
249
+
250
+ const panelRect = this.$refs.hiExpandPanelRef.getBoundingClientRect();
251
+ this.startWidth = panelRect.width;
252
+ this.startHeight = panelRect.height;
253
+
254
+ this.handleCreateMousemoveListener();
255
+ // 添加全局样式防止选中文本
256
+ document.body.style.userSelect = 'none';
257
+ },
258
+
259
+ // 核心逻辑:根据鼠标移动计算新的尺寸
260
+ handleDragMousemove(e) {
261
+ if (!this.isDragging) return;
262
+ // 只在需要阻止默认行为时调用 preventDefault
263
+ e.preventDefault();
264
+
265
+ let newSize;
266
+ if (this.isHorizontal) {
267
+ // 根据放置位置决定宽度变化的方向
268
+ const moveWidth = this.placement === 'left' ? this.startX - e.clientX : e.clientX - this.startX;
269
+ newSize = this.startWidth + moveWidth;
270
+ } else {
271
+ // 根据放置位置决定高度变化的方向
272
+ const moveHeight = this.placement === 'top' ? this.startY - e.clientY : e.clientY - this.startY;
273
+ newSize = this.startHeight + moveHeight;
274
+ }
275
+
276
+ // minSize 和 maxSize 已经限制了最大宽高,直接取 newSize 设置 clientSize 即可
277
+ this.clientSize = newSize;
278
+ },
279
+
280
+ // 鼠标松开结束拖动
281
+ handleDragMouseup() {
282
+ if (!this.isDragging) return;
283
+ this.isDragging = false;
284
+ this.handleSaveCache();
285
+ // 清理事件监听
286
+ this.handleClearMousemoveListener();
287
+ this.$emit('drag-end', {
288
+ size: this.clientSize,
289
+ expanded: this.innerExpanded
290
+ });
291
+
292
+ // 恢复全局样式
293
+ document.body.style.userSelect = '';
294
+ },
295
+
296
+ // 添加拖拽事件监听
297
+ handleCreateMousemoveListener() {
298
+ document.addEventListener('mousemove', this.handleDragMousemove);
299
+ document.addEventListener('mouseup', this.handleDragMouseup);
300
+ },
301
+
302
+ // 移除拖拽事件监听
303
+ handleClearMousemoveListener() {
304
+ document.removeEventListener('mousemove', this.handleDragMousemove);
305
+ document.removeEventListener('mouseup', this.handleDragMouseup);
306
+ }
307
+ }
308
+ };
309
+
310
+ function normalizeComponent$3(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
311
+ if (typeof shadowMode !== 'boolean') {
312
+ createInjectorSSR = createInjector;
313
+ createInjector = shadowMode;
314
+ shadowMode = false;
315
+ }
316
+ // Vue.extend constructor export interop.
317
+ const options = typeof script === 'function' ? script.options : script;
318
+ // render functions
319
+ if (template && template.render) {
320
+ options.render = template.render;
321
+ options.staticRenderFns = template.staticRenderFns;
322
+ options._compiled = true;
323
+ // functional template
324
+ if (isFunctionalTemplate) {
325
+ options.functional = true;
326
+ }
327
+ }
328
+ // scopedId
329
+ if (scopeId) {
330
+ options._scopeId = scopeId;
331
+ }
332
+ let hook;
333
+ if (moduleIdentifier) {
334
+ // server build
335
+ hook = function (context) {
336
+ // 2.3 injection
337
+ context =
338
+ context || // cached call
339
+ (this.$vnode && this.$vnode.ssrContext) || // stateful
340
+ (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional
341
+ // 2.2 with runInNewContext: true
342
+ if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
343
+ context = __VUE_SSR_CONTEXT__;
344
+ }
345
+ // inject component styles
346
+ if (style) {
347
+ style.call(this, createInjectorSSR(context));
348
+ }
349
+ // register component module identifier for async chunk inference
350
+ if (context && context._registeredComponents) {
351
+ context._registeredComponents.add(moduleIdentifier);
352
+ }
353
+ };
354
+ // used by ssr in case component is cached and beforeCreate
355
+ // never gets called
356
+ options._ssrRegister = hook;
357
+ }
358
+ else if (style) {
359
+ hook = shadowMode
360
+ ? function (context) {
361
+ style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));
362
+ }
363
+ : function (context) {
364
+ style.call(this, createInjector(context));
365
+ };
366
+ }
367
+ if (hook) {
368
+ if (options.functional) {
369
+ // register for functional component in vue file
370
+ const originalRender = options.render;
371
+ options.render = function renderWithStyleInjection(h, context) {
372
+ hook.call(context);
373
+ return originalRender(h, context);
374
+ };
375
+ }
376
+ else {
377
+ // inject component registration as beforeCreate hook
378
+ const existing = options.beforeCreate;
379
+ options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
380
+ }
381
+ }
382
+ return script;
383
+ }
384
+
385
+ const isOldIE$3 = typeof navigator !== 'undefined' &&
386
+ /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());
387
+ function createInjector$3(context) {
388
+ return (id, style) => addStyle$3(id, style);
389
+ }
390
+ let HEAD$3;
391
+ const styles$3 = {};
392
+ function addStyle$3(id, css) {
393
+ const group = isOldIE$3 ? css.media || 'default' : id;
394
+ const style = styles$3[group] || (styles$3[group] = { ids: new Set(), styles: [] });
395
+ if (!style.ids.has(id)) {
396
+ style.ids.add(id);
397
+ let code = css.source;
398
+ if (css.map) {
399
+ // https://developer.chrome.com/devtools/docs/javascript-debugging
400
+ // this makes source maps inside style tags work properly in Chrome
401
+ code += '\n/*# sourceURL=' + css.map.sources[0] + ' */';
402
+ // http://stackoverflow.com/a/26603875
403
+ code +=
404
+ '\n/*# sourceMappingURL=data:application/json;base64,' +
405
+ btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
406
+ ' */';
407
+ }
408
+ if (!style.element) {
409
+ style.element = document.createElement('style');
410
+ style.element.type = 'text/css';
411
+ if (css.media)
412
+ style.element.setAttribute('media', css.media);
413
+ if (HEAD$3 === undefined) {
414
+ HEAD$3 = document.head || document.getElementsByTagName('head')[0];
415
+ }
416
+ HEAD$3.appendChild(style.element);
417
+ }
418
+ if ('styleSheet' in style.element) {
419
+ style.styles.push(code);
420
+ style.element.styleSheet.cssText = style.styles
421
+ .filter(Boolean)
422
+ .join('\n');
423
+ }
424
+ else {
425
+ const index = style.ids.size - 1;
426
+ const textNode = document.createTextNode(code);
427
+ const nodes = style.element.childNodes;
428
+ if (nodes[index])
429
+ style.element.removeChild(nodes[index]);
430
+ if (nodes.length)
431
+ style.element.insertBefore(textNode, nodes[index]);
432
+ else
433
+ style.element.appendChild(textNode);
434
+ }
435
+ }
436
+ }
437
+
438
+ /* script */
439
+ const __vue_script__$3 = script$3;
440
+
441
+ /* template */
442
+ var __vue_render__$3 = function () {
443
+ var _vm = this;
444
+ var _h = _vm.$createElement;
445
+ var _c = _vm._self._c || _h;
446
+ return _c(
447
+ "div",
448
+ {
449
+ ref: "hiExpandPanelRef",
450
+ class: ["hi-expand-panel", "hi-expand-panel--" + _vm.placement],
451
+ style: _vm.panelStyle,
452
+ },
453
+ [
454
+ _c(
455
+ "div",
456
+ {
457
+ directives: [
458
+ {
459
+ name: "show",
460
+ rawName: "v-show",
461
+ value: _vm.innerExpanded,
462
+ expression: "innerExpanded",
463
+ },
464
+ ],
465
+ staticClass: "hi-expand-panel--content",
466
+ },
467
+ [_vm._t("default")],
468
+ 2
469
+ ),
470
+ _vm._v(" "),
471
+ _c(
472
+ "div",
473
+ {
474
+ class: [
475
+ "hi-expand-panel--control",
476
+ {
477
+ "hi-expand-panel--control-draggable":
478
+ _vm.draggable && _vm.innerExpanded,
479
+ "hi-expand-panel--control-dragging":
480
+ _vm.isDragging && _vm.innerExpanded,
481
+ },
482
+ ],
483
+ on: {
484
+ mousedown: _vm.handleDragMousedown,
485
+ mousemove: _vm.handleDragMousemove,
486
+ mouseup: _vm.handleDragMouseup,
487
+ },
488
+ },
489
+ [
490
+ _vm.$slots.trigger || _vm.showTrigger
491
+ ? _c(
492
+ "div",
493
+ {
494
+ staticClass: "hi-expand-panel--control-trigger",
495
+ on: {
496
+ click: function ($event) {
497
+ $event.stopPropagation();
498
+ return _vm.handleToggle.apply(null, arguments)
499
+ },
500
+ mousedown: function ($event) {
501
+ $event.stopPropagation();
502
+ },
503
+ },
504
+ },
505
+ [
506
+ _vm._t("trigger", function () {
507
+ return [
508
+ _vm.isHorizontal
509
+ ? [
510
+ _vm.placement === "right"
511
+ ? _c("span", [
512
+ _vm._v(_vm._s(_vm.innerExpanded ? "◂" : "▸")),
513
+ ])
514
+ : _c("span", [
515
+ _vm._v(_vm._s(_vm.innerExpanded ? "▸" : "◂")),
516
+ ]),
517
+ ]
518
+ : [
519
+ _vm.placement === "top"
520
+ ? _c("span", [
521
+ _vm._v(_vm._s(_vm.innerExpanded ? "▾" : "▴")),
522
+ ])
523
+ : _c("span", [
524
+ _vm._v(_vm._s(_vm.innerExpanded ? "▴" : "▾")),
525
+ ]),
526
+ ],
527
+ ]
528
+ }),
529
+ ],
530
+ 2
531
+ )
532
+ : _vm._e(),
533
+ ]
534
+ ),
535
+ ]
536
+ )
537
+ };
538
+ var __vue_staticRenderFns__$3 = [];
539
+ __vue_render__$3._withStripped = true;
540
+
541
+ /* style */
542
+ const __vue_inject_styles__$3 = function (inject) {
543
+ if (!inject) return
544
+ inject("data-v-0b2dbdef_0", { source: ".hi-expand-panel[data-v-0b2dbdef] {\n position: relative;\n display: flex;\n transition: flex 0.3s ease;\n}\n.hi-expand-panel:hover .hi-expand-panel--control-trigger[data-v-0b2dbdef] {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.hi-expand-panel--content[data-v-0b2dbdef] {\n height: 100%;\n width: 100%;\n overflow: auto;\n padding: 16px;\n box-sizing: border-box;\n}\n.hi-expand-panel--control-draggable[data-v-0b2dbdef]:hover {\n cursor: var(--control-draggable-cursor);\n}\n.hi-expand-panel--control-dragging[data-v-0b2dbdef] {\n background-color: var(--control-dragging-bg-color) !important;\n}\n.hi-expand-panel--right .hi-expand-panel--control[data-v-0b2dbdef],\n.hi-expand-panel--left .hi-expand-panel--control[data-v-0b2dbdef] {\n position: absolute;\n height: 100%;\n width: 1px;\n background-color: var(--td-gray-color-4);\n}\n.hi-expand-panel--right .hi-expand-panel--control-trigger[data-v-0b2dbdef],\n.hi-expand-panel--left .hi-expand-panel--control-trigger[data-v-0b2dbdef] {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n cursor: pointer;\n background-color: var(--td-font-white-1);\n border: 1px solid var(--td-gray-color-4);\n width: 12px;\n height: 56px;\n display: none;\n border-radius: 6px;\n}\n.hi-expand-panel--right[data-v-0b2dbdef] {\n flex-direction: row;\n}\n.hi-expand-panel--right .hi-expand-panel--control[data-v-0b2dbdef] {\n right: 0;\n}\n.hi-expand-panel--right .hi-expand-panel--control-trigger[data-v-0b2dbdef] {\n right: -6px;\n}\n.hi-expand-panel--left[data-v-0b2dbdef] {\n flex-direction: row-reverse;\n}\n.hi-expand-panel--left .hi-expand-panel--control[data-v-0b2dbdef] {\n left: 0;\n}\n.hi-expand-panel--left .hi-expand-panel--control-trigger[data-v-0b2dbdef] {\n left: -6px;\n}\n.hi-expand-panel--top .hi-expand-panel--control[data-v-0b2dbdef],\n.hi-expand-panel--bottom .hi-expand-panel--control[data-v-0b2dbdef] {\n position: absolute;\n width: 100%;\n height: 1px;\n background-color: var(--td-gray-color-4);\n}\n.hi-expand-panel--top .hi-expand-panel--control-trigger[data-v-0b2dbdef],\n.hi-expand-panel--bottom .hi-expand-panel--control-trigger[data-v-0b2dbdef] {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n cursor: pointer;\n background-color: var(--td-font-white-1);\n border: 1px solid var(--td-gray-color-4);\n width: 56px;\n height: 12px;\n display: none;\n border-radius: 6px;\n}\n.hi-expand-panel--top[data-v-0b2dbdef] {\n flex-direction: column-reverse;\n}\n.hi-expand-panel--top .hi-expand-panel--control[data-v-0b2dbdef] {\n top: 0;\n}\n.hi-expand-panel--top .hi-expand-panel--control-trigger[data-v-0b2dbdef] {\n top: -6px;\n}\n.hi-expand-panel--bottom[data-v-0b2dbdef] {\n flex-direction: column;\n}\n.hi-expand-panel--bottom .hi-expand-panel--control[data-v-0b2dbdef] {\n bottom: 0;\n}\n.hi-expand-panel--bottom .hi-expand-panel--control-trigger[data-v-0b2dbdef] {\n bottom: -6px;\n}\n", map: {"version":3,"sources":["index.vue"],"names":[],"mappings":"AAAA;EACE,kBAAkB;EAClB,aAAa;EACb,0BAA0B;AAC5B;AACA;EACE,aAAa;EACb,mBAAmB;EACnB,uBAAuB;AACzB;AACA;EACE,YAAY;EACZ,WAAW;EACX,cAAc;EACd,aAAa;EACb,sBAAsB;AACxB;AACA;EACE,uCAAuC;AACzC;AACA;EACE,6DAA6D;AAC/D;AACA;;EAEE,kBAAkB;EAClB,YAAY;EACZ,UAAU;EACV,wCAAwC;AAC1C;AACA;;EAEE,kBAAkB;EAClB,QAAQ;EACR,2BAA2B;EAC3B,eAAe;EACf,wCAAwC;EACxC,wCAAwC;EACxC,WAAW;EACX,YAAY;EACZ,aAAa;EACb,kBAAkB;AACpB;AACA;EACE,mBAAmB;AACrB;AACA;EACE,QAAQ;AACV;AACA;EACE,WAAW;AACb;AACA;EACE,2BAA2B;AAC7B;AACA;EACE,OAAO;AACT;AACA;EACE,UAAU;AACZ;AACA;;EAEE,kBAAkB;EAClB,WAAW;EACX,WAAW;EACX,wCAAwC;AAC1C;AACA;;EAEE,kBAAkB;EAClB,SAAS;EACT,2BAA2B;EAC3B,eAAe;EACf,wCAAwC;EACxC,wCAAwC;EACxC,WAAW;EACX,YAAY;EACZ,aAAa;EACb,kBAAkB;AACpB;AACA;EACE,8BAA8B;AAChC;AACA;EACE,MAAM;AACR;AACA;EACE,SAAS;AACX;AACA;EACE,sBAAsB;AACxB;AACA;EACE,SAAS;AACX;AACA;EACE,YAAY;AACd","file":"index.vue","sourcesContent":[".hi-expand-panel {\n position: relative;\n display: flex;\n transition: flex 0.3s ease;\n}\n.hi-expand-panel:hover .hi-expand-panel--control-trigger {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.hi-expand-panel--content {\n height: 100%;\n width: 100%;\n overflow: auto;\n padding: 16px;\n box-sizing: border-box;\n}\n.hi-expand-panel--control-draggable:hover {\n cursor: var(--control-draggable-cursor);\n}\n.hi-expand-panel--control-dragging {\n background-color: var(--control-dragging-bg-color) !important;\n}\n.hi-expand-panel--right .hi-expand-panel--control,\n.hi-expand-panel--left .hi-expand-panel--control {\n position: absolute;\n height: 100%;\n width: 1px;\n background-color: var(--td-gray-color-4);\n}\n.hi-expand-panel--right .hi-expand-panel--control-trigger,\n.hi-expand-panel--left .hi-expand-panel--control-trigger {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n cursor: pointer;\n background-color: var(--td-font-white-1);\n border: 1px solid var(--td-gray-color-4);\n width: 12px;\n height: 56px;\n display: none;\n border-radius: 6px;\n}\n.hi-expand-panel--right {\n flex-direction: row;\n}\n.hi-expand-panel--right .hi-expand-panel--control {\n right: 0;\n}\n.hi-expand-panel--right .hi-expand-panel--control-trigger {\n right: -6px;\n}\n.hi-expand-panel--left {\n flex-direction: row-reverse;\n}\n.hi-expand-panel--left .hi-expand-panel--control {\n left: 0;\n}\n.hi-expand-panel--left .hi-expand-panel--control-trigger {\n left: -6px;\n}\n.hi-expand-panel--top .hi-expand-panel--control,\n.hi-expand-panel--bottom .hi-expand-panel--control {\n position: absolute;\n width: 100%;\n height: 1px;\n background-color: var(--td-gray-color-4);\n}\n.hi-expand-panel--top .hi-expand-panel--control-trigger,\n.hi-expand-panel--bottom .hi-expand-panel--control-trigger {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n cursor: pointer;\n background-color: var(--td-font-white-1);\n border: 1px solid var(--td-gray-color-4);\n width: 56px;\n height: 12px;\n display: none;\n border-radius: 6px;\n}\n.hi-expand-panel--top {\n flex-direction: column-reverse;\n}\n.hi-expand-panel--top .hi-expand-panel--control {\n top: 0;\n}\n.hi-expand-panel--top .hi-expand-panel--control-trigger {\n top: -6px;\n}\n.hi-expand-panel--bottom {\n flex-direction: column;\n}\n.hi-expand-panel--bottom .hi-expand-panel--control {\n bottom: 0;\n}\n.hi-expand-panel--bottom .hi-expand-panel--control-trigger {\n bottom: -6px;\n}\n"]}, media: undefined });
545
+
546
+ };
547
+ /* scoped */
548
+ const __vue_scope_id__$3 = "data-v-0b2dbdef";
549
+ /* module identifier */
550
+ const __vue_module_identifier__$3 = undefined;
551
+ /* functional template */
552
+ const __vue_is_functional_template__$3 = false;
553
+ /* style inject SSR */
554
+
555
+ /* style inject shadow dom */
556
+
557
+
558
+
559
+ const __vue_component__$3 = /*#__PURE__*/normalizeComponent$3(
560
+ { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
561
+ __vue_inject_styles__$3,
562
+ __vue_script__$3,
563
+ __vue_scope_id__$3,
564
+ __vue_is_functional_template__$3,
565
+ __vue_module_identifier__$3,
566
+ false,
567
+ createInjector$3,
568
+ undefined,
569
+ undefined
570
+ );
571
+
572
+ __vue_component__$3.name = 'HiExpandPanel';
573
+
574
+ // 添加 install
575
+ __vue_component__$3.install = Vue => {
576
+ Vue.component(__vue_component__$3.name, __vue_component__$3);
577
+ };
578
+
579
+ //
580
+ //
581
+ //
582
+ //
583
+ //
584
+ //
585
+ //
586
+ //
587
+ //
588
+ //
589
+ //
590
+
591
+ var script$2 = {
592
+ name: 'HiExpandText',
593
+ props: {
594
+ content: {
595
+ type: String,
596
+ default: ''
597
+ },
598
+
599
+ /**
600
+ * [0]: 展开时显示的文本,[1]: 收起时显示的文本
601
+ */
602
+ label: {
603
+ type: Array,
604
+ default: () => ['展开', '收起'],
605
+ validator: arr => {
606
+ return arr.length === 2 && arr.every(s => typeof s === 'string');
607
+ }
608
+ },
609
+
610
+ lineClamp: {
611
+ type: Number,
612
+ default: 2
613
+ }
614
+ },
615
+ data() {
616
+ return {
617
+ showToggle: false,
618
+ isExpanded: false,
619
+ resizeTimer: null,
620
+ resizeObserver: null
621
+ };
622
+ },
623
+ computed: {
624
+ expandText() {
625
+ return this.isExpanded ? this.label[1] : this.label[0];
626
+ },
627
+
628
+ textClass() {
629
+ return {
630
+ 'hi-expand-text--content': true,
631
+ 'hi-expand-text--content__show-toggle': this.showToggle,
632
+ 'hi-expand-text--content__expanded': this.isExpanded
633
+ };
634
+ }
635
+ },
636
+
637
+ watch: {
638
+ content() {
639
+ this.$nextTick(this.checkEllipsis);
640
+ }
641
+ },
642
+ mounted() {
643
+ this.checkEllipsis();
644
+ window.addEventListener('resize', this.handleResize);
645
+ if (window.ResizeObserver && this.$el) {
646
+ this.resizeObserver = new ResizeObserver(() => {
647
+ this.handleResize();
648
+ });
649
+ this.resizeObserver.observe(this.$el);
650
+ }
651
+ },
652
+ beforeDestroy() {
653
+ window.removeEventListener('resize', this.handleResize);
654
+ if (this.resizeObserver) this.resizeObserver.disconnect();
655
+ if (this.resizeTimer) clearTimeout(this.resizeTimer);
656
+ },
657
+
658
+ methods: {
659
+ handleResize() {
660
+ if (this.resizeTimer) clearTimeout(this.resizeTimer);
661
+ this.resizeTimer = setTimeout(() => {
662
+ this.checkEllipsis();
663
+ }, 100);
664
+ },
665
+
666
+ /**
667
+ * @Description 检查是否存在溢出情况,兼容展开和收起两种状态
668
+ * @Author holyer
669
+ * @Date 2026/02/08 17:01:51
670
+ */
671
+ checkEllipsis() {
672
+ const textEl = this.$refs.textRef;
673
+ if (!textEl || !textEl.offsetParent) {
674
+ this.showToggle = false;
675
+ return;
676
+ }
677
+
678
+ if (this.isExpanded) {
679
+ // 展开状态下:模拟收起状态,检测是否需要 toggle
680
+ this.showToggle = this.wouldOverflowIfCollapsed(textEl);
681
+ } else {
682
+ // 收起状态下:直接检测是否溢出
683
+ this.showToggle = textEl.scrollHeight - textEl.clientHeight > 2;
684
+ }
685
+ },
686
+
687
+ /**
688
+ * 模拟收起状态,检测内容是否会溢出
689
+ */
690
+ wouldOverflowIfCollapsed(el) {
691
+ // 1. 保存原始状态
692
+ const originalDisplay = el.style.display;
693
+ const originalWebkitLineClamp = el.style.webkitLineClamp;
694
+ const originalClassList = el.className;
695
+
696
+ try {
697
+ // 2. 临时应用“收起”样式,移除 --expanded 和 --show-toggle
698
+ el.className = 'hi-expand-text--content';
699
+ el.style.display = '-webkit-box';
700
+ el.style.webkitBoxOrient = 'vertical';
701
+ el.style.overflow = 'hidden';
702
+ el.style.webkitLineClamp = this.lineClamp;
703
+
704
+ // 3. 强制 reflow(触发 layout)
705
+ const { scrollHeight, clientHeight } = el;
706
+
707
+ // 4. 判断是否溢出
708
+ return scrollHeight - clientHeight > 2;
709
+ } finally {
710
+ // 5. 恢复原始状态(确保无副作用)
711
+ el.className = originalClassList;
712
+ el.style.display = originalDisplay;
713
+ el.style.webkitLineClamp = originalWebkitLineClamp;
714
+ }
715
+ },
716
+
717
+ handleToggle() {
718
+ this.isExpanded = !this.isExpanded;
719
+ this.$emit('toggle', this.isExpanded);
720
+ },
721
+
722
+ // 供外部手动更新
723
+ update() {
724
+ this.$nextTick(this.checkEllipsis);
725
+ }
726
+ }
727
+ };
728
+
729
+ function normalizeComponent$2(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
730
+ if (typeof shadowMode !== 'boolean') {
731
+ createInjectorSSR = createInjector;
732
+ createInjector = shadowMode;
733
+ shadowMode = false;
734
+ }
735
+ // Vue.extend constructor export interop.
736
+ const options = typeof script === 'function' ? script.options : script;
737
+ // render functions
738
+ if (template && template.render) {
739
+ options.render = template.render;
740
+ options.staticRenderFns = template.staticRenderFns;
741
+ options._compiled = true;
742
+ // functional template
743
+ if (isFunctionalTemplate) {
744
+ options.functional = true;
745
+ }
746
+ }
747
+ // scopedId
748
+ if (scopeId) {
749
+ options._scopeId = scopeId;
750
+ }
751
+ let hook;
752
+ if (moduleIdentifier) {
753
+ // server build
754
+ hook = function (context) {
755
+ // 2.3 injection
756
+ context =
757
+ context || // cached call
758
+ (this.$vnode && this.$vnode.ssrContext) || // stateful
759
+ (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional
760
+ // 2.2 with runInNewContext: true
761
+ if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
762
+ context = __VUE_SSR_CONTEXT__;
763
+ }
764
+ // inject component styles
765
+ if (style) {
766
+ style.call(this, createInjectorSSR(context));
767
+ }
768
+ // register component module identifier for async chunk inference
769
+ if (context && context._registeredComponents) {
770
+ context._registeredComponents.add(moduleIdentifier);
771
+ }
772
+ };
773
+ // used by ssr in case component is cached and beforeCreate
774
+ // never gets called
775
+ options._ssrRegister = hook;
776
+ }
777
+ else if (style) {
778
+ hook = shadowMode
779
+ ? function (context) {
780
+ style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));
781
+ }
782
+ : function (context) {
783
+ style.call(this, createInjector(context));
784
+ };
785
+ }
786
+ if (hook) {
787
+ if (options.functional) {
788
+ // register for functional component in vue file
789
+ const originalRender = options.render;
790
+ options.render = function renderWithStyleInjection(h, context) {
791
+ hook.call(context);
792
+ return originalRender(h, context);
793
+ };
794
+ }
795
+ else {
796
+ // inject component registration as beforeCreate hook
797
+ const existing = options.beforeCreate;
798
+ options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
799
+ }
800
+ }
801
+ return script;
802
+ }
803
+
804
+ const isOldIE$2 = typeof navigator !== 'undefined' &&
805
+ /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());
806
+ function createInjector$2(context) {
807
+ return (id, style) => addStyle$2(id, style);
808
+ }
809
+ let HEAD$2;
810
+ const styles$2 = {};
811
+ function addStyle$2(id, css) {
812
+ const group = isOldIE$2 ? css.media || 'default' : id;
813
+ const style = styles$2[group] || (styles$2[group] = { ids: new Set(), styles: [] });
814
+ if (!style.ids.has(id)) {
815
+ style.ids.add(id);
816
+ let code = css.source;
817
+ if (css.map) {
818
+ // https://developer.chrome.com/devtools/docs/javascript-debugging
819
+ // this makes source maps inside style tags work properly in Chrome
820
+ code += '\n/*# sourceURL=' + css.map.sources[0] + ' */';
821
+ // http://stackoverflow.com/a/26603875
822
+ code +=
823
+ '\n/*# sourceMappingURL=data:application/json;base64,' +
824
+ btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
825
+ ' */';
826
+ }
827
+ if (!style.element) {
828
+ style.element = document.createElement('style');
829
+ style.element.type = 'text/css';
830
+ if (css.media)
831
+ style.element.setAttribute('media', css.media);
832
+ if (HEAD$2 === undefined) {
833
+ HEAD$2 = document.head || document.getElementsByTagName('head')[0];
834
+ }
835
+ HEAD$2.appendChild(style.element);
836
+ }
837
+ if ('styleSheet' in style.element) {
838
+ style.styles.push(code);
839
+ style.element.styleSheet.cssText = style.styles
840
+ .filter(Boolean)
841
+ .join('\n');
842
+ }
843
+ else {
844
+ const index = style.ids.size - 1;
845
+ const textNode = document.createTextNode(code);
846
+ const nodes = style.element.childNodes;
847
+ if (nodes[index])
848
+ style.element.removeChild(nodes[index]);
849
+ if (nodes.length)
850
+ style.element.insertBefore(textNode, nodes[index]);
851
+ else
852
+ style.element.appendChild(textNode);
853
+ }
854
+ }
855
+ }
856
+
857
+ /* script */
858
+ const __vue_script__$2 = script$2;
859
+
860
+ /* template */
861
+ var __vue_render__$2 = function () {
862
+ var _vm = this;
863
+ var _h = _vm.$createElement;
864
+ var _c = _vm._self._c || _h;
865
+ return _c(
866
+ "div",
867
+ {
868
+ staticClass: "hi-expand-text",
869
+ style: { "--hi-expand-text-line-clamp": _vm.lineClamp },
870
+ },
871
+ [
872
+ _c(
873
+ "div",
874
+ { ref: "textRef", class: _vm.textClass },
875
+ [
876
+ _vm.showToggle
877
+ ? _c(
878
+ "div",
879
+ {
880
+ staticClass: "hi-expand-text--toggle",
881
+ on: { click: _vm.handleToggle },
882
+ },
883
+ [
884
+ _vm._t("toggleText", function () {
885
+ return [_vm._v(_vm._s(_vm.expandText))]
886
+ }),
887
+ ],
888
+ 2
889
+ )
890
+ : _vm._e(),
891
+ _vm._v(" "),
892
+ _vm._t("default", function () {
893
+ return [_vm._v(_vm._s(_vm.content))]
894
+ }),
895
+ ],
896
+ 2
897
+ ),
898
+ ]
899
+ )
900
+ };
901
+ var __vue_staticRenderFns__$2 = [];
902
+ __vue_render__$2._withStripped = true;
903
+
904
+ /* style */
905
+ const __vue_inject_styles__$2 = function (inject) {
906
+ if (!inject) return
907
+ inject("data-v-3bd7513c_0", { source: ".hi-expand-text[data-v-3bd7513c] {\n display: flex;\n width: 100%;\n line-height: 1.5;\n}\n.hi-expand-text--content[data-v-3bd7513c] {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n -webkit-line-clamp: var(--hi-expand-text-line-clamp);\n line-clamp: var(--hi-expand-text-line-clamp);\n}\n.hi-expand-text--content.hi-expand-text--content__show-toggle[data-v-3bd7513c]::before {\n content: '';\n float: right;\n height: 100%;\n margin-bottom: calc(-1em * 1.5);\n}\n.hi-expand-text--content__expanded[data-v-3bd7513c] {\n display: block;\n overflow: visible;\n -webkit-line-clamp: unset;\n line-clamp: unset;\n}\n.hi-expand-text--toggle[data-v-3bd7513c] {\n color: var(--td-brand-color);\n float: right;\n clear: both;\n cursor: pointer;\n}\n", map: {"version":3,"sources":["index.vue"],"names":[],"mappings":"AAAA;EACE,aAAa;EACb,WAAW;EACX,gBAAgB;AAClB;AACA;EACE,oBAAoB;EACpB,4BAA4B;EAC5B,gBAAgB;EAChB,uBAAuB;EACvB,sBAAsB;EACtB,oDAAoD;EACpD,4CAA4C;AAC9C;AACA;EACE,WAAW;EACX,YAAY;EACZ,YAAY;EACZ,+BAA+B;AACjC;AACA;EACE,cAAc;EACd,iBAAiB;EACjB,yBAAyB;EACzB,iBAAiB;AACnB;AACA;EACE,4BAA4B;EAC5B,YAAY;EACZ,WAAW;EACX,eAAe;AACjB","file":"index.vue","sourcesContent":[".hi-expand-text {\n display: flex;\n width: 100%;\n line-height: 1.5;\n}\n.hi-expand-text--content {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n -webkit-line-clamp: var(--hi-expand-text-line-clamp);\n line-clamp: var(--hi-expand-text-line-clamp);\n}\n.hi-expand-text--content.hi-expand-text--content__show-toggle::before {\n content: '';\n float: right;\n height: 100%;\n margin-bottom: calc(-1em * 1.5);\n}\n.hi-expand-text--content__expanded {\n display: block;\n overflow: visible;\n -webkit-line-clamp: unset;\n line-clamp: unset;\n}\n.hi-expand-text--toggle {\n color: var(--td-brand-color);\n float: right;\n clear: both;\n cursor: pointer;\n}\n"]}, media: undefined });
908
+
909
+ };
910
+ /* scoped */
911
+ const __vue_scope_id__$2 = "data-v-3bd7513c";
912
+ /* module identifier */
913
+ const __vue_module_identifier__$2 = undefined;
914
+ /* functional template */
915
+ const __vue_is_functional_template__$2 = false;
916
+ /* style inject SSR */
917
+
918
+ /* style inject shadow dom */
919
+
920
+
921
+
922
+ const __vue_component__$2 = /*#__PURE__*/normalizeComponent$2(
923
+ { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
924
+ __vue_inject_styles__$2,
925
+ __vue_script__$2,
926
+ __vue_scope_id__$2,
927
+ __vue_is_functional_template__$2,
928
+ __vue_module_identifier__$2,
929
+ false,
930
+ createInjector$2,
931
+ undefined,
932
+ undefined
933
+ );
934
+
935
+ __vue_component__$2.name = 'HiExpandText';
936
+
937
+ // 添加 install
938
+ __vue_component__$2.install = Vue => {
939
+ Vue.component(__vue_component__$2.name, __vue_component__$2);
940
+ };
941
+
942
+ //
943
+ //
944
+ //
945
+ //
946
+ //
947
+ //
948
+ //
949
+ //
950
+ //
951
+ //
952
+ //
953
+ //
954
+ //
955
+ //
956
+ //
957
+ //
958
+ //
959
+ //
960
+ //
961
+ //
962
+ //
963
+ //
964
+ //
965
+ //
966
+ //
967
+ //
17
968
  //
18
969
  //
19
970
  //
@@ -21,15 +972,585 @@ function __$styleInject(css) {
21
972
  //
22
973
  //
23
974
 
24
- var script = {
975
+ const TITILE_SIZE_MAP = {
976
+ small: '14px',
977
+ medium: '16px',
978
+ large: '18px'
979
+ };
980
+ var script$1 = {
25
981
  name: 'HiTitle',
982
+ inheritAttrs: false,
26
983
  props: {
984
+ // 自定义图标类名
985
+ iconClass: {
986
+ type: String,
987
+ default: ''
988
+ },
989
+ // 主标题文本(优先级低于 default slot)
27
990
  content: {
28
991
  type: String,
29
- default: '标题'
992
+ default: ''
993
+ },
994
+ // 描述文本(优先级低于 description slot)
995
+ description: {
996
+ type: String,
997
+ default: ''
998
+ },
999
+ // 尺寸:控制整体大小(影响文字、图标、bar 高度)
1000
+ size: {
1001
+ type: String,
1002
+ default: 'medium',
1003
+ validator: val => ['small', 'medium', 'large'].includes(val)
1004
+ },
1005
+ // 主标题颜色(支持自定义)
1006
+ color: {
1007
+ type: String,
1008
+ default: ''
1009
+ },
1010
+ // 自定义前缀图标组件
1011
+ prefixIcon: {
1012
+ type: [Object, Function],
1013
+ default: null
1014
+ },
1015
+ // 自定义 bar 类名(用于覆盖样式)
1016
+ barClass: {
1017
+ type: String,
1018
+ default: ''
1019
+ },
1020
+ // 自定义标题文字类名
1021
+ textClass: {
1022
+ type: String,
1023
+ default: ''
1024
+ },
1025
+ // 自定义描述文字类名
1026
+ descClass: {
1027
+ type: String,
1028
+ default: ''
1029
+ }
1030
+ },
1031
+ computed: {
1032
+ titleClass() {
1033
+ return ['hi-title', `hi-title--${this.size}`, { 'hi-title--has-desc': this.hasDescription }];
1034
+ },
1035
+ // 根据 size 计算文字大小
1036
+ textSize() {
1037
+ return TITILE_SIZE_MAP[this.size] || TITILE_SIZE_MAP.medium;
1038
+ },
1039
+ // 图标大小 = 文字大小(保持视觉一致)
1040
+ iconSize() {
1041
+ return this.textSize;
1042
+ },
1043
+ // 装饰条高度 = 文字行高 ≈ 文字大小 * 1.2~1.5
1044
+ barHeight() {
1045
+ const base = parseFloat(TITILE_SIZE_MAP[this.size] || '16');
1046
+ return `${base * 1.2}px`;
1047
+ },
1048
+ // 主标题颜色(props 优先,否则继承)
1049
+ textColor() {
1050
+ return this.color || 'inherit';
1051
+ },
1052
+ // 是否存在描述内容(用于控制布局)
1053
+ hasDescription() {
1054
+ return this.description || this.$slots.description;
1055
+ }
1056
+ }
1057
+ };
1058
+
1059
+ function normalizeComponent$1(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
1060
+ if (typeof shadowMode !== 'boolean') {
1061
+ createInjectorSSR = createInjector;
1062
+ createInjector = shadowMode;
1063
+ shadowMode = false;
1064
+ }
1065
+ // Vue.extend constructor export interop.
1066
+ const options = typeof script === 'function' ? script.options : script;
1067
+ // render functions
1068
+ if (template && template.render) {
1069
+ options.render = template.render;
1070
+ options.staticRenderFns = template.staticRenderFns;
1071
+ options._compiled = true;
1072
+ // functional template
1073
+ if (isFunctionalTemplate) {
1074
+ options.functional = true;
1075
+ }
1076
+ }
1077
+ // scopedId
1078
+ if (scopeId) {
1079
+ options._scopeId = scopeId;
1080
+ }
1081
+ let hook;
1082
+ if (moduleIdentifier) {
1083
+ // server build
1084
+ hook = function (context) {
1085
+ // 2.3 injection
1086
+ context =
1087
+ context || // cached call
1088
+ (this.$vnode && this.$vnode.ssrContext) || // stateful
1089
+ (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional
1090
+ // 2.2 with runInNewContext: true
1091
+ if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
1092
+ context = __VUE_SSR_CONTEXT__;
1093
+ }
1094
+ // inject component styles
1095
+ if (style) {
1096
+ style.call(this, createInjectorSSR(context));
1097
+ }
1098
+ // register component module identifier for async chunk inference
1099
+ if (context && context._registeredComponents) {
1100
+ context._registeredComponents.add(moduleIdentifier);
1101
+ }
1102
+ };
1103
+ // used by ssr in case component is cached and beforeCreate
1104
+ // never gets called
1105
+ options._ssrRegister = hook;
1106
+ }
1107
+ else if (style) {
1108
+ hook = shadowMode
1109
+ ? function (context) {
1110
+ style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));
1111
+ }
1112
+ : function (context) {
1113
+ style.call(this, createInjector(context));
1114
+ };
1115
+ }
1116
+ if (hook) {
1117
+ if (options.functional) {
1118
+ // register for functional component in vue file
1119
+ const originalRender = options.render;
1120
+ options.render = function renderWithStyleInjection(h, context) {
1121
+ hook.call(context);
1122
+ return originalRender(h, context);
1123
+ };
1124
+ }
1125
+ else {
1126
+ // inject component registration as beforeCreate hook
1127
+ const existing = options.beforeCreate;
1128
+ options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
1129
+ }
1130
+ }
1131
+ return script;
1132
+ }
1133
+
1134
+ const isOldIE$1 = typeof navigator !== 'undefined' &&
1135
+ /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());
1136
+ function createInjector$1(context) {
1137
+ return (id, style) => addStyle$1(id, style);
1138
+ }
1139
+ let HEAD$1;
1140
+ const styles$1 = {};
1141
+ function addStyle$1(id, css) {
1142
+ const group = isOldIE$1 ? css.media || 'default' : id;
1143
+ const style = styles$1[group] || (styles$1[group] = { ids: new Set(), styles: [] });
1144
+ if (!style.ids.has(id)) {
1145
+ style.ids.add(id);
1146
+ let code = css.source;
1147
+ if (css.map) {
1148
+ // https://developer.chrome.com/devtools/docs/javascript-debugging
1149
+ // this makes source maps inside style tags work properly in Chrome
1150
+ code += '\n/*# sourceURL=' + css.map.sources[0] + ' */';
1151
+ // http://stackoverflow.com/a/26603875
1152
+ code +=
1153
+ '\n/*# sourceMappingURL=data:application/json;base64,' +
1154
+ btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
1155
+ ' */';
1156
+ }
1157
+ if (!style.element) {
1158
+ style.element = document.createElement('style');
1159
+ style.element.type = 'text/css';
1160
+ if (css.media)
1161
+ style.element.setAttribute('media', css.media);
1162
+ if (HEAD$1 === undefined) {
1163
+ HEAD$1 = document.head || document.getElementsByTagName('head')[0];
1164
+ }
1165
+ HEAD$1.appendChild(style.element);
1166
+ }
1167
+ if ('styleSheet' in style.element) {
1168
+ style.styles.push(code);
1169
+ style.element.styleSheet.cssText = style.styles
1170
+ .filter(Boolean)
1171
+ .join('\n');
1172
+ }
1173
+ else {
1174
+ const index = style.ids.size - 1;
1175
+ const textNode = document.createTextNode(code);
1176
+ const nodes = style.element.childNodes;
1177
+ if (nodes[index])
1178
+ style.element.removeChild(nodes[index]);
1179
+ if (nodes.length)
1180
+ style.element.insertBefore(textNode, nodes[index]);
1181
+ else
1182
+ style.element.appendChild(textNode);
1183
+ }
1184
+ }
1185
+ }
1186
+
1187
+ /* script */
1188
+ const __vue_script__$1 = script$1;
1189
+
1190
+ /* template */
1191
+ var __vue_render__$1 = function () {
1192
+ var _vm = this;
1193
+ var _h = _vm.$createElement;
1194
+ var _c = _vm._self._c || _h;
1195
+ return _c(
1196
+ "div",
1197
+ _vm._b({ class: _vm.titleClass }, "div", _vm.$attrs, false),
1198
+ [
1199
+ _c("div", { staticClass: "hi-title__header" }, [
1200
+ _c(
1201
+ "div",
1202
+ { staticClass: "hi-title__prefix" },
1203
+ [
1204
+ _vm._t("prefix", function () {
1205
+ return [
1206
+ _vm.prefixIcon
1207
+ ? _c(_vm.prefixIcon, {
1208
+ tag: "component",
1209
+ class: ["hi-title__icon", _vm.iconClass],
1210
+ style: { fontSize: _vm.iconSize },
1211
+ })
1212
+ : _c("div", {
1213
+ class: ["hi-title__bar", _vm.barClass],
1214
+ style: { height: _vm.barHeight },
1215
+ }),
1216
+ ]
1217
+ }),
1218
+ ],
1219
+ 2
1220
+ ),
1221
+ _vm._v(" "),
1222
+ _vm.content || _vm.$slots.default
1223
+ ? _c(
1224
+ "div",
1225
+ {
1226
+ class: ["hi-title__text", _vm.textClass],
1227
+ style: {
1228
+ color: _vm.textColor,
1229
+ fontSize: _vm.textSize,
1230
+ },
1231
+ },
1232
+ [
1233
+ _vm._t("default", function () {
1234
+ return [_vm._v(_vm._s(_vm.content))]
1235
+ }),
1236
+ ],
1237
+ 2
1238
+ )
1239
+ : _vm._e(),
1240
+ ]),
1241
+ _vm._v(" "),
1242
+ _vm.hasDescription
1243
+ ? _c(
1244
+ "p",
1245
+ { class: ["hi-title__description", _vm.descClass] },
1246
+ [
1247
+ _vm._t("description", function () {
1248
+ return [_vm._v(_vm._s(_vm.description))]
1249
+ }),
1250
+ ],
1251
+ 2
1252
+ )
1253
+ : _vm._e(),
1254
+ ]
1255
+ )
1256
+ };
1257
+ var __vue_staticRenderFns__$1 = [];
1258
+ __vue_render__$1._withStripped = true;
1259
+
1260
+ /* style */
1261
+ const __vue_inject_styles__$1 = function (inject) {
1262
+ if (!inject) return
1263
+ inject("data-v-2948bff0_0", { source: ".hi-title[data-v-2948bff0] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.hi-title--small[data-v-2948bff0] {\n gap: 2px;\n}\n.hi-title--large[data-v-2948bff0] {\n gap: 6px;\n}\n.hi-title__header[data-v-2948bff0] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.hi-title__prefix[data-v-2948bff0] {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n}\n.hi-title__bar[data-v-2948bff0] {\n width: 4px;\n background-color: var(--td-brand-color);\n flex-shrink: 0;\n}\n.hi-title__icon[data-v-2948bff0] {\n flex-shrink: 0;\n line-height: 1;\n}\n.hi-title__text[data-v-2948bff0] {\n margin: 0;\n font-weight: 600;\n color: var(--td-text-color-primary);\n}\n.hi-title__description[data-v-2948bff0] {\n margin: 0;\n font-size: 12px;\n color: var(--td-text-color-secondary);\n}\n", map: {"version":3,"sources":["index.vue"],"names":[],"mappings":"AAAA;EACE,aAAa;EACb,sBAAsB;EACtB,QAAQ;AACV;AACA;EACE,QAAQ;AACV;AACA;EACE,QAAQ;AACV;AACA;EACE,aAAa;EACb,mBAAmB;EACnB,SAAS;AACX;AACA;EACE,cAAc;EACd,aAAa;EACb,mBAAmB;AACrB;AACA;EACE,UAAU;EACV,uCAAuC;EACvC,cAAc;AAChB;AACA;EACE,cAAc;EACd,cAAc;AAChB;AACA;EACE,SAAS;EACT,gBAAgB;EAChB,mCAAmC;AACrC;AACA;EACE,SAAS;EACT,eAAe;EACf,qCAAqC;AACvC","file":"index.vue","sourcesContent":[".hi-title {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.hi-title--small {\n gap: 2px;\n}\n.hi-title--large {\n gap: 6px;\n}\n.hi-title__header {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.hi-title__prefix {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n}\n.hi-title__bar {\n width: 4px;\n background-color: var(--td-brand-color);\n flex-shrink: 0;\n}\n.hi-title__icon {\n flex-shrink: 0;\n line-height: 1;\n}\n.hi-title__text {\n margin: 0;\n font-weight: 600;\n color: var(--td-text-color-primary);\n}\n.hi-title__description {\n margin: 0;\n font-size: 12px;\n color: var(--td-text-color-secondary);\n}\n"]}, media: undefined });
1264
+
1265
+ };
1266
+ /* scoped */
1267
+ const __vue_scope_id__$1 = "data-v-2948bff0";
1268
+ /* module identifier */
1269
+ const __vue_module_identifier__$1 = undefined;
1270
+ /* functional template */
1271
+ const __vue_is_functional_template__$1 = false;
1272
+ /* style inject SSR */
1273
+
1274
+ /* style inject shadow dom */
1275
+
1276
+
1277
+
1278
+ const __vue_component__$1 = /*#__PURE__*/normalizeComponent$1(
1279
+ { render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 },
1280
+ __vue_inject_styles__$1,
1281
+ __vue_script__$1,
1282
+ __vue_scope_id__$1,
1283
+ __vue_is_functional_template__$1,
1284
+ __vue_module_identifier__$1,
1285
+ false,
1286
+ createInjector$1,
1287
+ undefined,
1288
+ undefined
1289
+ );
1290
+
1291
+ // 显式设置 name(避免 .vue 丢失)
1292
+ __vue_component__$1.name = 'HiTitle';
1293
+
1294
+ // 添加 install
1295
+ __vue_component__$1.install = Vue => {
1296
+ Vue.component(__vue_component__$1.name, __vue_component__$1);
1297
+ };
1298
+
1299
+ /**
1300
+ * 类型判断工具
1301
+ */
1302
+
1303
+ /**
1304
+ * 将数值或字符串转换为合法的 CSS 长度值(用于 style 绑定)
1305
+ * - 若为 number,自动追加 'px' 单位
1306
+ * - 若为 string,原样返回(假定用户已提供合法 CSS 长度值)
1307
+ * @param {number|string} value - 输入值(如 100, '100%', '50vh', 'auto')
1308
+ * @returns {string} 合法的 CSS 长度字符串 默认值为 0
1309
+ * @example
1310
+ * formatSize(100) // '100px'
1311
+ * formatSize('100%') // '100%'
1312
+ * formatSize('50vh') // '50vh'
1313
+ * formatSize(null) // 0
1314
+ * formatSize(undefined) // 0
1315
+ */
1316
+ function formatSize(value) {
1317
+ if (typeof value === 'number') {
1318
+ return `${value}px`;
1319
+ }
1320
+ if (typeof value === 'string') {
1321
+ if (value.trim() === '') {
1322
+ return 0;
1323
+ }
1324
+ return value;
1325
+ }
1326
+ return 0;
1327
+ }
1328
+
1329
+ //
1330
+
1331
+ var script = {
1332
+ name: 'HiVirtualList',
1333
+ props: {
1334
+ items: {
1335
+ type: Array,
1336
+ required: true,
1337
+ default: () => []
1338
+ },
1339
+ itemHeight: {
1340
+ type: Number,
1341
+ required: true,
1342
+ validator(value) {
1343
+ if (value <= 0) {
1344
+ // eslint-disable-next-line no-console
1345
+ console.error('[HiVirtualList] itemHeight must be a positive number.');
1346
+ return false;
1347
+ }
1348
+ return true;
1349
+ }
1350
+ },
1351
+ height: {
1352
+ type: [Number, String],
1353
+ required: true
1354
+ },
1355
+ buffer: {
1356
+ type: Number,
1357
+ default: 50
1358
+ },
1359
+ nodeKey: {
1360
+ type: String,
1361
+ default: undefined
1362
+ }
1363
+ },
1364
+ data() {
1365
+ return {
1366
+ scrollTop: 0,
1367
+ clientHeight: 0,
1368
+ resizeObserver: null,
1369
+ resizeTimer: null
1370
+ };
1371
+ },
1372
+ computed: {
1373
+ styles() {
1374
+ return {
1375
+ height: formatSize(this.height)
1376
+ };
1377
+ },
1378
+ renderItemHeight() {
1379
+ return formatSize(this.itemHeight);
1380
+ },
1381
+ total() {
1382
+ return this.items.length;
1383
+ },
1384
+ visibleCount() {
1385
+ if (!this.clientHeight || !this.itemHeight) return 20;
1386
+ return Math.ceil(this.clientHeight / this.itemHeight) + this.buffer * 2;
1387
+ },
1388
+ startIndex() {
1389
+ return Math.max(0, Math.floor(this.scrollTop / this.itemHeight));
1390
+ },
1391
+ endIndex() {
1392
+ return Math.min(this.total, this.startIndex + this.visibleCount);
1393
+ },
1394
+ visibleItems() {
1395
+ return this.items.slice(this.startIndex, this.endIndex);
1396
+ },
1397
+ topPlaceholderHeight() {
1398
+ return formatSize(this.startIndex * this.itemHeight);
1399
+ },
1400
+ bottomPlaceholderHeight() {
1401
+ return formatSize((this.total - this.endIndex) * this.itemHeight);
30
1402
  }
31
1403
  },
32
- data() {}
1404
+ mounted() {
1405
+ this.updateClientHeight();
1406
+ this.setupResizeListener();
1407
+ },
1408
+ beforeDestroy() {
1409
+ this.cleanupResizeListener();
1410
+ },
1411
+ methods: {
1412
+ /**
1413
+ * 判断是否应使用 nodeKey 字段
1414
+ */
1415
+ _shouldUseNodeKey() {
1416
+ return this.nodeKey && typeof this.nodeKey === 'string' && this.nodeKey.trim() !== '';
1417
+ },
1418
+
1419
+ /**
1420
+ * 获取 item 的唯一 key(用于 v-for)
1421
+ */
1422
+ getNodeKey(item, index) {
1423
+ if (this._shouldUseNodeKey() && item != null && typeof item === 'object') {
1424
+ const key = item[this.nodeKey];
1425
+ if (key != null) {
1426
+ return key;
1427
+ }
1428
+ }
1429
+ return index;
1430
+ },
1431
+
1432
+ updateClientHeight() {
1433
+ if (this.$refs.rootRef) {
1434
+ this.clientHeight = this.$refs.rootRef.clientHeight;
1435
+ }
1436
+ },
1437
+
1438
+ setupResizeListener() {
1439
+ if (typeof ResizeObserver !== 'undefined') {
1440
+ this.resizeObserver = new ResizeObserver(() => {
1441
+ this.updateClientHeight();
1442
+ });
1443
+ this.resizeObserver.observe(this.$refs.rootRef);
1444
+ } else {
1445
+ window.addEventListener('resize', this.handleWindowResize);
1446
+ }
1447
+ },
1448
+
1449
+ cleanupResizeListener() {
1450
+ if (this.resizeObserver) {
1451
+ this.resizeObserver.disconnect();
1452
+ this.resizeObserver = null;
1453
+ }
1454
+ if (this.resizeTimer) {
1455
+ clearTimeout(this.resizeTimer);
1456
+ this.resizeTimer = null;
1457
+ }
1458
+ window.removeEventListener('resize', this.handleWindowResize);
1459
+ },
1460
+
1461
+ handleWindowResize() {
1462
+ if (this.resizeTimer) {
1463
+ clearTimeout(this.resizeTimer);
1464
+ }
1465
+ this.resizeTimer = setTimeout(() => {
1466
+ this.updateClientHeight();
1467
+ }, 100);
1468
+ },
1469
+
1470
+ handleScroll(e) {
1471
+ this.scrollTop = e.target.scrollTop;
1472
+ this.$emit('scroll', e);
1473
+
1474
+ const { scrollHeight, clientHeight, scrollTop } = e.target;
1475
+ if (scrollTop === 0) this.$emit('reach-top');
1476
+ if (scrollHeight - clientHeight - scrollTop < 100) this.$emit('reach-bottom');
1477
+ this.$emit('visible-change', { startIndex: this.startIndex, endIndex: this.endIndex });
1478
+ },
1479
+
1480
+ refresh() {
1481
+ this.updateClientHeight();
1482
+ },
1483
+
1484
+ /**
1485
+ * 内部滚动方法(仅设置 DOM scrollTop,状态由 handleScroll 同步)
1486
+ */
1487
+ _setScrollTop(scrollTop) {
1488
+ if (this.$refs.rootRef) {
1489
+ this.$refs.rootRef.scrollTop = scrollTop;
1490
+ }
1491
+ },
1492
+
1493
+ /**
1494
+ * 滚动到指定目标
1495
+ * - 若启用了有效的 nodeKey,则 target 视为 keyValue
1496
+ * - 否则,target 必须是 number(index)
1497
+ */
1498
+ scrollTo(target) {
1499
+ if (target == null) {
1500
+ // eslint-disable-next-line no-console
1501
+ console.warn('[HiVirtualList] scrollTo: target cannot be null or undefined');
1502
+ return;
1503
+ }
1504
+
1505
+ let index = -1;
1506
+
1507
+ // 使用 getNodeKey 生成的 key 进行匹配(严格对齐)
1508
+ if (this._shouldUseNodeKey()) {
1509
+ index = this.items.findIndex((item, i) => {
1510
+ return this.getNodeKey(item, i) === target;
1511
+ });
1512
+ } else {
1513
+ // 降级到 index 模式
1514
+ if (typeof target === 'number' && target >= 0 && target < this.total) {
1515
+ index = target;
1516
+ } else {
1517
+ // eslint-disable-next-line no-console
1518
+ console.warn(
1519
+ '[HiVirtualList] scrollTo: when nodeKey is not provided, target must be a valid index (number).'
1520
+ );
1521
+ return;
1522
+ }
1523
+ }
1524
+
1525
+ if (index < 0 || index >= this.total) {
1526
+ if (this._shouldUseNodeKey()) {
1527
+ // eslint-disable-next-line no-console
1528
+ console.warn(`[HiVirtualList] scrollTo: item with key "${target}" not found`);
1529
+ } else {
1530
+ // eslint-disable-next-line no-console
1531
+ console.warn(`[HiVirtualList] scrollTo: index ${target} is out of range [0, ${this.total})`);
1532
+ }
1533
+ return;
1534
+ }
1535
+
1536
+ this._setScrollTop(index * this.itemHeight);
1537
+ },
1538
+
1539
+ scrollToTop() {
1540
+ this._setScrollTop(0);
1541
+ },
1542
+
1543
+ scrollToBottom() {
1544
+ this._setScrollTop(this.total * this.itemHeight);
1545
+ },
1546
+
1547
+ getVisibleRange() {
1548
+ return {
1549
+ startIndex: this.startIndex,
1550
+ endIndex: this.endIndex
1551
+ };
1552
+ }
1553
+ }
33
1554
  };
34
1555
 
35
1556
  function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
@@ -168,9 +1689,40 @@ var __vue_render__ = function () {
168
1689
  var _vm = this;
169
1690
  var _h = _vm.$createElement;
170
1691
  var _c = _vm._self._c || _h;
171
- return _c("div", { staticClass: "hi-title" }, [
172
- _vm._v("\n " + _vm._s(_vm.content) + "\n"),
173
- ])
1692
+ return _c(
1693
+ "div",
1694
+ {
1695
+ ref: "rootRef",
1696
+ staticClass: "hi-virtual-list",
1697
+ style: _vm.styles,
1698
+ on: { scroll: _vm.handleScroll },
1699
+ },
1700
+ [
1701
+ _c("div", {
1702
+ staticClass: "hi-virtual-list--placeholder",
1703
+ style: { height: _vm.topPlaceholderHeight },
1704
+ }),
1705
+ _vm._v(" "),
1706
+ _vm._l(_vm.visibleItems, function (item, i) {
1707
+ return _c(
1708
+ "div",
1709
+ {
1710
+ key: _vm.getNodeKey(item, _vm.startIndex + i),
1711
+ staticClass: "hi-virtual-list--item-wrapper",
1712
+ style: { height: _vm.renderItemHeight },
1713
+ },
1714
+ [_vm._t("default", null, { item: item, index: _vm.startIndex + i })],
1715
+ 2
1716
+ )
1717
+ }),
1718
+ _vm._v(" "),
1719
+ _c("div", {
1720
+ staticClass: "hi-virtual-list--placeholder",
1721
+ style: { height: _vm.bottomPlaceholderHeight },
1722
+ }),
1723
+ ],
1724
+ 2
1725
+ )
174
1726
  };
175
1727
  var __vue_staticRenderFns__ = [];
176
1728
  __vue_render__._withStripped = true;
@@ -178,11 +1730,11 @@ __vue_render__._withStripped = true;
178
1730
  /* style */
179
1731
  const __vue_inject_styles__ = function (inject) {
180
1732
  if (!inject) return
181
- inject("data-v-03a3ffda_0", { source: "\n.title[data-v-03a3ffda] {\n text-align: center;\n margin: 20px 0;\n}\n", map: {"version":3,"sources":["/home/runner/work/holyer-lib/holyer-lib/packages/ui/title/src/index.vue"],"names":[],"mappings":";AAoBA;EACA,kBAAA;EACA,cAAA;AACA","file":"index.vue","sourcesContent":["<template>\n <div class=\"hi-title\">\n {{ content }}\n </div>\n</template>\n\n<script>\nexport default {\n name: 'HiTitle',\n props: {\n content: {\n type: String,\n default: '标题'\n }\n },\n data() {}\n};\n</script>\n\n<style scoped>\n.title {\n text-align: center;\n margin: 20px 0;\n}\n</style>\n"]}, media: undefined });
1733
+ inject("data-v-7fe73d95_0", { source: ".hi-virtual-list[data-v-7fe73d95] {\n overflow-y: auto;\n overflow-x: hidden;\n position: relative;\n will-change: scroll-position;\n}\n.hi-virtual-list--placeholder[data-v-7fe73d95] {\n pointer-events: none;\n}\n.hi-virtual-list--item-wrapper[data-v-7fe73d95] {\n box-sizing: border-box;\n}\n", map: {"version":3,"sources":["index.vue"],"names":[],"mappings":"AAAA;EACE,gBAAgB;EAChB,kBAAkB;EAClB,kBAAkB;EAClB,4BAA4B;AAC9B;AACA;EACE,oBAAoB;AACtB;AACA;EACE,sBAAsB;AACxB","file":"index.vue","sourcesContent":[".hi-virtual-list {\n overflow-y: auto;\n overflow-x: hidden;\n position: relative;\n will-change: scroll-position;\n}\n.hi-virtual-list--placeholder {\n pointer-events: none;\n}\n.hi-virtual-list--item-wrapper {\n box-sizing: border-box;\n}\n"]}, media: undefined });
182
1734
 
183
1735
  };
184
1736
  /* scoped */
185
- const __vue_scope_id__ = "data-v-03a3ffda";
1737
+ const __vue_scope_id__ = "data-v-7fe73d95";
186
1738
  /* module identifier */
187
1739
  const __vue_module_identifier__ = undefined;
188
1740
  /* functional template */
@@ -206,7 +1758,21 @@ __vue_render__._withStripped = true;
206
1758
  undefined
207
1759
  );
208
1760
 
209
- const components = [__vue_component__];
1761
+ // 显式设置 name(避免 .vue 丢失)
1762
+ __vue_component__.name = 'HiVirtualList';
1763
+
1764
+ // 添加 install
1765
+ __vue_component__.install = Vue => {
1766
+ Vue.component(__vue_component__.name, __vue_component__);
1767
+ };
1768
+
1769
+ // eslint-disable-next-line prettier/prettier
1770
+ const components = [
1771
+ __vue_component__$3,
1772
+ __vue_component__$2,
1773
+ __vue_component__$1,
1774
+ __vue_component__
1775
+ ];
210
1776
 
211
1777
  const install = function (Vue) {
212
1778
  components.forEach(component => {
@@ -214,6 +1780,12 @@ const install = function (Vue) {
214
1780
  });
215
1781
  };
216
1782
 
217
- var index = { install, HiTitle: __vue_component__ };
1783
+ var index = {
1784
+ install,
1785
+ HiExpandPanel: __vue_component__$3,
1786
+ HiExpandText: __vue_component__$2,
1787
+ HiTitle: __vue_component__$1,
1788
+ HiVirtualList: __vue_component__
1789
+ };
218
1790
 
219
1791
  module.exports = index;