@veltra/desktop 1.7.7 → 1.7.9

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.
Files changed (58) hide show
  1. package/dist/components/cascade/use-check.js +5 -2
  2. package/dist/components/cascade/use-check.js.map +1 -1
  3. package/dist/components/cascade/use-select.js +5 -1
  4. package/dist/components/cascade/use-select.js.map +1 -1
  5. package/dist/components/code-editor/langs/bash/index.js +1 -1
  6. package/dist/components/code-editor/langs/powershell/index.js +1 -1
  7. package/dist/components/image-cropper/draw-crop.js +63 -0
  8. package/dist/components/image-cropper/draw-crop.js.map +1 -0
  9. package/dist/components/image-cropper/image-cropper.js +344 -0
  10. package/dist/components/image-cropper/image-cropper.js.map +1 -0
  11. package/dist/components/image-cropper/style.d.ts +1 -0
  12. package/dist/components/image-cropper/style.js +1 -0
  13. package/dist/components/image-cropper/style2.css +205 -0
  14. package/dist/components/image-cropper/use-image-loader.js +59 -0
  15. package/dist/components/image-cropper/use-image-loader.js.map +1 -0
  16. package/dist/components/image-cropper/use-preview.js +44 -0
  17. package/dist/components/image-cropper/use-preview.js.map +1 -0
  18. package/dist/components/image-cropper/use-selection.js +183 -0
  19. package/dist/components/image-cropper/use-selection.js.map +1 -0
  20. package/dist/components/image-cropper/use-transform.js +182 -0
  21. package/dist/components/image-cropper/use-transform.js.map +1 -0
  22. package/dist/components/index.d.ts +2 -1
  23. package/dist/components/index.js +3 -1
  24. package/dist/components/index.js.map +1 -1
  25. package/dist/components/table/style2.css +1 -1
  26. package/dist/index.d.ts +47 -45
  27. package/dist/index.js +46 -45
  28. package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/powershell.js +1 -1
  29. package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/powershell.js.map +1 -1
  30. package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/shell.js +1 -1
  31. package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/shell.js.map +1 -1
  32. package/dist/types/cascade.d.ts +6 -2
  33. package/dist/types/image-cropper.d.ts +68 -0
  34. package/dist/types/index.d.ts +2 -1
  35. package/package.json +15 -15
  36. package/src/components/auto-complete/style.scss +3 -15
  37. package/src/components/cascade/__test__/cascade.test.ts +103 -0
  38. package/src/components/cascade/use-check.ts +5 -2
  39. package/src/components/cascade/use-select.ts +2 -1
  40. package/src/components/contextmenu/style.scss +4 -20
  41. package/src/components/image-cropper/__test__/draw-crop.test.ts +131 -0
  42. package/src/components/image-cropper/__test__/image-cropper.test.ts +744 -0
  43. package/src/components/image-cropper/__test__/use-transform.test.ts +229 -0
  44. package/src/components/image-cropper/draw-crop.ts +112 -0
  45. package/src/components/image-cropper/image-cropper.vue +302 -0
  46. package/src/components/image-cropper/index.ts +1 -0
  47. package/src/components/image-cropper/style.scss +232 -0
  48. package/src/components/image-cropper/style.ts +1 -0
  49. package/src/components/image-cropper/use-image-loader.ts +79 -0
  50. package/src/components/image-cropper/use-preview.ts +58 -0
  51. package/src/components/image-cropper/use-selection.ts +255 -0
  52. package/src/components/image-cropper/use-transform.ts +256 -0
  53. package/src/components/index.ts +2 -0
  54. package/src/components/select/style.scss +3 -15
  55. package/src/components/table/style.scss +1 -1
  56. package/src/types/cascade.ts +10 -2
  57. package/src/types/image-cropper.ts +73 -0
  58. package/src/types/index.ts +1 -0
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { createApp, h, nextTick, ref } from 'vue'
3
+
4
+ import UCascade from '../cascade.vue'
5
+
6
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
7
+
8
+ const cascadeData = [
9
+ {
10
+ label: '浙江省',
11
+ value: 'zhejiang',
12
+ children: [
13
+ {
14
+ label: '杭州市',
15
+ value: 'hangzhou',
16
+ children: [
17
+ { label: '西湖区', value: 'xihu' },
18
+ { label: '余杭区', value: 'yuhang' }
19
+ ]
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ label: '江苏省',
25
+ value: 'jiangsu',
26
+ children: [
27
+ { label: '南京市', value: 'nanjing', children: [{ label: '玄武区', value: 'xuanwu' }] }
28
+ ]
29
+ }
30
+ ]
31
+
32
+ function queryCascadeItems() {
33
+ return [...document.body.querySelectorAll<HTMLElement>('.u-cascade-panel-item__option')]
34
+ }
35
+
36
+ async function openDropdown(host: HTMLElement) {
37
+ const trigger = (host.querySelector('.u-cascade') ||
38
+ host.querySelector('.u-dropdown') ||
39
+ host.firstElementChild) as HTMLElement
40
+ trigger.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
41
+ for (let attempt = 0; attempt < 20; attempt++) {
42
+ await nextTick()
43
+ await sleep(25)
44
+ if (queryCascadeItems().length > 0) return
45
+ }
46
+ }
47
+
48
+ async function clickItemByLabel(label: string) {
49
+ const item = queryCascadeItems().find((el) => el.textContent?.includes(label))
50
+ expect(item, `cascade item "${label}" not found`).toBeTruthy()
51
+ item!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
52
+ await nextTick()
53
+ await sleep(30)
54
+ }
55
+
56
+ describe('UCascade change event fullLabel', () => {
57
+ it('should emit fullLabel in change event for single select', async () => {
58
+ const host = document.createElement('div')
59
+ document.body.appendChild(host)
60
+
61
+ const model = ref<string | undefined>()
62
+ let changeResult: { item: any; fullLabel?: string } | undefined
63
+
64
+ const app = createApp({
65
+ render() {
66
+ return h(UCascade, {
67
+ data: cascadeData,
68
+ modelValue: model.value,
69
+ 'onUpdate:modelValue': (val: string) => {
70
+ model.value = val
71
+ },
72
+ onChange: (item: any, fullLabel?: string) => {
73
+ changeResult = { item, fullLabel }
74
+ }
75
+ })
76
+ }
77
+ })
78
+
79
+ app.mount(host)
80
+ await nextTick()
81
+
82
+ await openDropdown(host)
83
+
84
+ // 点击第一列 "浙江省"
85
+ await clickItemByLabel('浙江省')
86
+
87
+ // 点击第二列 "杭州市"
88
+ await clickItemByLabel('杭州市')
89
+
90
+ // 点击第三列 "西湖区"
91
+ await clickItemByLabel('西湖区')
92
+
93
+ expect(changeResult).toBeDefined()
94
+ expect(changeResult?.fullLabel).toBe('浙江省/杭州市/西湖区')
95
+ expect(changeResult?.item).toBeDefined()
96
+ expect(changeResult?.item.value).toBe('xihu')
97
+ expect(changeResult?.item.label).toBe('西湖区')
98
+ expect(changeResult?.item.fullLabel).toBe('浙江省/杭州市/西湖区')
99
+
100
+ app.unmount()
101
+ host.remove()
102
+ })
103
+ })
@@ -68,10 +68,13 @@ export function useCheck(options: CheckOptions): UseCheckReturned {
68
68
  const checkedArr = Array.from(checkedSet.value)
69
69
  const targetValues = checkedArr.map((item) => item.value)
70
70
  const targetLabels = checkedArr.map((item) => item.label)
71
- const targetItems = checkedArr.map((item) => item.data as Record<string, any>)
71
+ const targetItems = checkedArr.map((item) => {
72
+ const data = item.data as Record<string, any>
73
+ return data ? Object.assign({}, data, { fullLabel: item.label }) : data
74
+ })
72
75
  emit('update:modelValue', targetValues)
73
76
  emit('update:label', targetLabels.length ? targetLabels : undefined)
74
- emit('change', targetItems)
77
+ emit('change', targetItems, targetLabels.length ? targetLabels : undefined)
75
78
  }
76
79
 
77
80
  const restTag = computed(() => {
@@ -115,10 +115,11 @@ export function useSelect(options: SelectOptions): UseSelectReturned {
115
115
  : leafLabel
116
116
 
117
117
  const leafNode = leafKey ? dataMap.value.get(leafKey) : undefined
118
+ const changeItem = leafNode?.data ? { ...leafNode.data, fullLabel: targetLabel } : undefined
118
119
 
119
120
  emit('update:modelValue', targetValue)
120
121
  emit('update:label', targetLabel)
121
- emit('change', leafNode?.data as Record<string, any> | undefined)
122
+ emit('change', changeItem, targetLabel)
122
123
  }
123
124
 
124
125
  /**
@@ -92,31 +92,15 @@ $arrow-size: 12px;
92
92
 
93
93
  #{$b}__item:not(.is-disabled):not(.is-loading) > & {
94
94
  &:hover {
95
- background-color: fn.component-var(
96
- contextmenu,
97
- item-hover-bg,
98
- fn.use-var(bg-color, hover)
99
- );
100
- color: fn.component-var(
101
- contextmenu,
102
- item-hover-color,
103
- fn.use-var(text-color, title)
104
- );
95
+ background-color: fn.component-var(contextmenu, item-hover-bg, fn.use-var(bg-color, hover));
96
+ color: fn.component-var(contextmenu, item-hover-color, fn.use-var(text-color, title));
105
97
  }
106
98
  }
107
99
  }
108
100
 
109
101
  #{$b}__item.is-open:not(.is-disabled) > #{$b}__item-content {
110
- background-color: fn.component-var(
111
- contextmenu,
112
- item-hover-bg,
113
- fn.use-var(bg-color, hover)
114
- );
115
- color: fn.component-var(
116
- contextmenu,
117
- item-hover-color,
118
- fn.use-var(text-color, title)
119
- );
102
+ background-color: fn.component-var(contextmenu, item-hover-bg, fn.use-var(bg-color, hover));
103
+ color: fn.component-var(contextmenu, item-hover-color, fn.use-var(text-color, title));
120
104
  }
121
105
 
122
106
  @include m.e(icon-place) {
@@ -0,0 +1,131 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ import { cropToResult, drawCropToCanvas } from '../draw-crop'
4
+
5
+ /** 记录调用参数的 2d 上下文桩 */
6
+ function createCtxStub() {
7
+ return { translate: vi.fn(), rotate: vi.fn(), scale: vi.fn(), drawImage: vi.fn() }
8
+ }
9
+
10
+ function createCanvasStub(ctx: ReturnType<typeof createCtxStub>) {
11
+ return { width: 0, height: 0, getContext: () => ctx } as unknown as HTMLCanvasElement
12
+ }
13
+
14
+ const img = {} as HTMLImageElement
15
+ const selection = { x: 10, y: 20, width: 200, height: 100 }
16
+ const identity = { rotation: 0, flipX: false, flipY: false }
17
+
18
+ describe('drawCropToCanvas', () => {
19
+ it('无变换时按选区原图像素尺寸输出', () => {
20
+ const ctx = createCtxStub()
21
+ const canvas = createCanvasStub(ctx)
22
+
23
+ drawCropToCanvas(img, selection, identity, canvas)
24
+
25
+ expect(canvas.width).toBe(200)
26
+ expect(canvas.height).toBe(100)
27
+ expect(ctx.rotate).toHaveBeenCalledWith(0)
28
+ expect(ctx.scale).toHaveBeenCalledWith(1, 1)
29
+ expect(ctx.drawImage).toHaveBeenCalledWith(img, 10, 20, 200, 100, -100, -50, 200, 100)
30
+ })
31
+
32
+ it('旋转 90° / 270° 时输出尺寸交换宽高', () => {
33
+ const ctx = createCtxStub()
34
+ const canvas = createCanvasStub(ctx)
35
+
36
+ drawCropToCanvas(img, selection, { ...identity, rotation: 90 }, canvas)
37
+
38
+ expect(canvas.width).toBe(100)
39
+ expect(canvas.height).toBe(200)
40
+ expect(ctx.rotate).toHaveBeenCalledWith(Math.PI / 2)
41
+ // 变换顺序与舞台一致:先旋转后缩放(翻转)
42
+ expect(ctx.translate).toHaveBeenCalledWith(50, 100)
43
+ })
44
+
45
+ it('翻转以负缩放体现', () => {
46
+ const ctx = createCtxStub()
47
+ const canvas = createCanvasStub(ctx)
48
+
49
+ drawCropToCanvas(img, selection, { ...identity, flipX: true, flipY: true }, canvas)
50
+
51
+ expect(ctx.scale).toHaveBeenCalledWith(-1, -1)
52
+ })
53
+
54
+ it('传入输出尺寸时按该尺寸缩放输出', () => {
55
+ const ctx = createCtxStub()
56
+ const canvas = createCanvasStub(ctx)
57
+
58
+ drawCropToCanvas(img, selection, identity, canvas, { width: 400, height: 200 })
59
+
60
+ expect(canvas.width).toBe(400)
61
+ expect(canvas.height).toBe(200)
62
+ expect(ctx.scale).toHaveBeenCalledWith(2, 2)
63
+ })
64
+
65
+ it('输出尺寸只传一个维度时另一维按选区比例推算', () => {
66
+ const ctx = createCtxStub()
67
+ const canvas = createCanvasStub(ctx)
68
+
69
+ drawCropToCanvas(img, selection, identity, canvas, { width: 100 })
70
+ expect(canvas.width).toBe(100)
71
+ expect(canvas.height).toBe(50)
72
+
73
+ drawCropToCanvas(img, selection, identity, canvas, { height: 400 })
74
+ expect(canvas.width).toBe(800)
75
+ expect(canvas.height).toBe(400)
76
+ })
77
+
78
+ it('选区像素为小数时输出尺寸取整', () => {
79
+ const ctx = createCtxStub()
80
+ const canvas = createCanvasStub(ctx)
81
+
82
+ drawCropToCanvas(img, { x: 0, y: 0, width: 100.4, height: 50.6 }, identity, canvas)
83
+
84
+ expect(canvas.width).toBe(100)
85
+ expect(canvas.height).toBe(51)
86
+ })
87
+ })
88
+
89
+ describe('cropToResult', () => {
90
+ afterEach(() => {
91
+ vi.restoreAllMocks()
92
+ })
93
+
94
+ it('默认按原图选区像素导出 Blob + base64 dataURL', async () => {
95
+ const toBlobSpy = vi.spyOn(HTMLCanvasElement.prototype, 'toBlob')
96
+
97
+ const { blob, base64 } = await cropToResult(img, selection, identity)
98
+
99
+ // 离屏画布按选区 200×100 输出
100
+ const canvas = toBlobSpy.mock.instances[0] as unknown as HTMLCanvasElement
101
+ expect(canvas.width).toBe(200)
102
+ expect(canvas.height).toBe(100)
103
+ expect(blob).toBeInstanceOf(Blob)
104
+ expect(blob.type).toBe('image/png')
105
+ expect(base64.startsWith('data:image/png;base64,')).toBe(true)
106
+ })
107
+
108
+ it('传入输出尺寸时按该尺寸缩放导出', async () => {
109
+ const toBlobSpy = vi.spyOn(HTMLCanvasElement.prototype, 'toBlob')
110
+
111
+ await cropToResult(img, selection, identity, { width: 100 })
112
+
113
+ const canvas = toBlobSpy.mock.instances[0] as unknown as HTMLCanvasElement
114
+ expect(canvas.width).toBe(100)
115
+ expect(canvas.height).toBe(50)
116
+ })
117
+
118
+ it('canvas 被跨域图片污染(toBlob 抛错)时拒绝为可捕获错误', async () => {
119
+ vi.spyOn(HTMLCanvasElement.prototype, 'toBlob').mockImplementation(() => {
120
+ throw new DOMException('The canvas has been tainted by cross-origin data.', 'SecurityError')
121
+ })
122
+
123
+ await expect(cropToResult(img, selection, identity)).rejects.toThrow(/tainted/)
124
+ })
125
+
126
+ it('toBlob 未产出数据时拒绝而不是静默返回', async () => {
127
+ vi.spyOn(HTMLCanvasElement.prototype, 'toBlob').mockImplementation((cb) => cb(null))
128
+
129
+ await expect(cropToResult(img, selection, identity)).rejects.toThrow(/got null/)
130
+ })
131
+ })