@mt2025ui/mt-design 1.0.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.
Files changed (36) hide show
  1. package/.vscode/extensions.json +3 -0
  2. package/LICENSE +21 -0
  3. package/README.md +5 -0
  4. package/index.html +14 -0
  5. package/package.json +25 -0
  6. package/public/vite.svg +1 -0
  7. package/src/App.vue +142 -0
  8. package/src/assets/iconfont/iconfont.js +70 -0
  9. package/src/assets/svg/DragIcon.vue +14 -0
  10. package/src/assets/svg/LockIcon.vue +14 -0
  11. package/src/assets/svg/UnlockIcon.vue +14 -0
  12. package/src/assets/vue.svg +1 -0
  13. package/src/components/MtAttach/MtAttachmentDisplay.vue +240 -0
  14. package/src/components/MtAttach/MtAttachmentUpload.vue +138 -0
  15. package/src/components/MtIcon/MtIcon.vue +48 -0
  16. package/src/components/MtIcon/index.ts +4 -0
  17. package/src/components/MtLayout/MtContainer.vue +97 -0
  18. package/src/components/MtLayout/MtFloatingPanel.vue +583 -0
  19. package/src/components/MtLayout/MtLayout.vue +99 -0
  20. package/src/components/MtLayout/MtLayoutItem.vue +1049 -0
  21. package/src/components/MtLayout/icons/CloseIcon.vue +7 -0
  22. package/src/components/MtLayout/icons/LockIcon.vue +7 -0
  23. package/src/components/MtLayout/icons/MenuIcon.vue +5 -0
  24. package/src/components/MtLayout/icons/UnlockIcon.vue +7 -0
  25. package/src/components/MtLayout/index.ts +7 -0
  26. package/src/components/MtLayout/registry.ts +15 -0
  27. package/src/example/mtFloating/Demo.vue +266 -0
  28. package/src/example/mtIcon/Demo.vue +151 -0
  29. package/src/example/mtLayout/Demo.vue +105 -0
  30. package/src/index.ts +29 -0
  31. package/src/main.ts +7 -0
  32. package/src/style.css +88 -0
  33. package/tsconfig.app.json +17 -0
  34. package/tsconfig.json +7 -0
  35. package/tsconfig.node.json +26 -0
  36. package/vite.config.ts +31 -0
@@ -0,0 +1,583 @@
1
+ <template>
2
+ <div
3
+ class="mt-floating-panel"
4
+ :class="{
5
+ 'is-collapsed': isCollapsed,
6
+ 'is-mini': mini,
7
+ 'is-interacting': isDragging || isResizing,
8
+ }"
9
+ :style="itemStyle"
10
+ ref="itemRef"
11
+ >
12
+ <!-- Header -->
13
+ <div class="mt-float-header" @mousedown="onHeaderMouseDown">
14
+ <!-- Left: Icon & Title -->
15
+ <div class="mt-header-left" v-if="!mini">
16
+ <div class="mt-icon">
17
+ <slot name="icon">
18
+ <!-- Default Icon (e.g. simple rect) -->
19
+ <svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
20
+ <path d="M1 2h14v12H1z" fill="none" stroke="currentColor" />
21
+ <path d="M1 5h14" stroke="currentColor" />
22
+ </svg>
23
+ </slot>
24
+ </div>
25
+ <span class="mt-title">{{ title }}</span>
26
+ </div>
27
+
28
+ <!-- Right: Actions -->
29
+ <div class="mt-header-right" v-if="!mini">
30
+ <button
31
+ v-if="collapsible"
32
+ class="mt-icon-btn collapse-btn"
33
+ @mousedown.stop
34
+ @click="toggleCollapse"
35
+ >
36
+ <svg
37
+ viewBox="0 0 16 16"
38
+ width="12"
39
+ height="12"
40
+ fill="currentColor"
41
+ class="arrow"
42
+ :class="{ rotated: isCollapsed }"
43
+ >
44
+ <path d="M8 11L3 6h10z" />
45
+ </svg>
46
+ </button>
47
+ <button class="mt-icon-btn close-btn" @mousedown.stop @click="close">
48
+ <svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
49
+ <path
50
+ d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
51
+ />
52
+ </svg>
53
+ </button>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Content -->
58
+ <div class="mt-float-content" v-show="!isCollapsed">
59
+ <slot></slot>
60
+ </div>
61
+
62
+ <!-- Resize Handle -->
63
+ <div
64
+ v-if="resizable && !isCollapsed"
65
+ class="mt-resize-handle"
66
+ @mousedown="onResizeMouseDown"
67
+ ></div>
68
+ </div>
69
+ </template>
70
+
71
+ <script setup lang="ts">
72
+ import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
73
+
74
+ const props = defineProps({
75
+ title: {
76
+ type: String,
77
+ default: "Floating Panel",
78
+ },
79
+ width: {
80
+ type: [Number, String],
81
+ default: 300,
82
+ },
83
+ height: {
84
+ type: [Number, String],
85
+ default: 400,
86
+ },
87
+ x: {
88
+ type: String as () => "left" | "right",
89
+ default: "right",
90
+ },
91
+ y: {
92
+ type: String as () => "top" | "bottom",
93
+ default: "bottom",
94
+ },
95
+ xOffset: {
96
+ type: Number,
97
+ default: 20,
98
+ },
99
+ yOffset: {
100
+ type: Number,
101
+ default: 20,
102
+ },
103
+ collapsible: {
104
+ type: Boolean,
105
+ default: true,
106
+ },
107
+ defaultCollapsed: {
108
+ type: Boolean,
109
+ default: false,
110
+ },
111
+ mini: {
112
+ type: Boolean,
113
+ default: false,
114
+ },
115
+ resizable: {
116
+ type: Boolean,
117
+ default: false,
118
+ },
119
+ heightOffset: {
120
+ type: Number,
121
+ default: 0,
122
+ },
123
+ });
124
+
125
+ const emit = defineEmits(["close"]);
126
+
127
+ const isCollapsed = ref(props.defaultCollapsed);
128
+ const itemRef = ref<HTMLElement | null>(null);
129
+ const isDragging = ref(false);
130
+ const isResizing = ref(false);
131
+
132
+ // Current positions (offsets from the configured edge)
133
+ const currentX = ref(props.xOffset);
134
+ const currentY = ref(props.yOffset);
135
+ const currentHeight = ref<number | string>(props.height);
136
+
137
+ const itemStyle = computed(() => {
138
+ const style: any = {
139
+ position: "absolute",
140
+ zIndex: 1000,
141
+ width: typeof props.width === "number" ? `${props.width}px` : props.width,
142
+ };
143
+
144
+ // If not collapsed, apply height
145
+ if (!isCollapsed.value) {
146
+ let h = currentHeight.value;
147
+
148
+ // If props.height is a percentage string (e.g., "50%"), we want to apply calculation: calc(50% - offset)
149
+ // If it's a number (pixels), we apply: number - offset
150
+
151
+ if (h === "auto" || h === undefined || h === null) {
152
+ style.height = "auto";
153
+ } else {
154
+ // Convert unitless string "400" to number 400
155
+ let numericH: number | null = null;
156
+
157
+ if (typeof h === "number") {
158
+ numericH = h;
159
+ } else if (typeof h === "string" && /^\d+(\.\d+)?$/.test(h)) {
160
+ numericH = parseFloat(h);
161
+ }
162
+
163
+ if (numericH !== null) {
164
+ // It's a numeric value (pixels)
165
+ style.height = `${Math.max(0, numericH + props.heightOffset)}px`;
166
+ } else if (typeof h === "string") {
167
+ // It's a string with units (e.g. "50%", "100vh", "300px")
168
+ if (props.heightOffset !== 0) {
169
+ const sign = props.heightOffset > 0 ? "+" : "-";
170
+ style.height = `calc(${h} ${sign} ${Math.abs(props.heightOffset)}px)`;
171
+ } else {
172
+ style.height = h;
173
+ }
174
+ }
175
+ }
176
+ } else {
177
+ style.height = "auto";
178
+ }
179
+
180
+ // Positioning
181
+ if (props.x === "left") {
182
+ style.left = `${currentX.value}px`;
183
+ style.right = "auto";
184
+ } else {
185
+ style.right = `${currentX.value}px`;
186
+ style.left = "auto";
187
+ }
188
+
189
+ if (props.y === "top") {
190
+ style.top = `${currentY.value}px`;
191
+ style.bottom = "auto";
192
+ } else {
193
+ // If anchored to bottom, ensure it doesn't disappear when collapsed
194
+ // If collapsed, the height is just the header (32px or 8px for mini).
195
+ // If currentY (offset from bottom) is very small, it's fine.
196
+ // BUT if the logic implies dragging might have pushed it down?
197
+ // Wait, the user requirement says: "If positioning is bottom, when collapsed,
198
+ // the bottom value cannot be less than the component header height."
199
+ // This sounds like they want to prevent it from being hidden if the parent is too small?
200
+ // Or maybe they mean "If I collapse it, it shouldn't shrink 'down' off screen"?
201
+
202
+ // Actually, "bottom value" usually means `bottom: 20px`.
203
+ // If I collapse it, the `bottom` property stays `20px`. The top moves down.
204
+ // This is standard CSS behavior for `bottom` positioning.
205
+
206
+ // However, if the user means "don't let the header go below the viewport",
207
+ // that's usually handled by drag constraints.
208
+
209
+ // Re-reading: "bottom的值不能小于组件标题栏的高度" -> "The bottom value cannot be less than the component header height"
210
+ // This might mean ensuring `currentY` is at least 32px?
211
+ // Or does it mean "Don't let the top of the collapsed header go below the bottom of the screen"?
212
+ // If `bottom: 0`, the header is at the bottom edge. Visible.
213
+ // If `bottom: -32px`, it's hidden.
214
+
215
+ // Let's assume they want to enforce a minimum offset when collapsed to keep it accessible.
216
+ // Or maybe they mean when you collapse it, it should snap to the bottom?
217
+
218
+ // Let's implement a constraint: effective bottom offset >= 0. (Which is already the default for drag)
219
+
220
+ // Wait, maybe they mean during DRAG?
221
+ // "收起时" means "When collapsed".
222
+ // "bottom的值" means the `bottom` CSS property (which is `currentY`).
223
+ // "不能小于组件标题栏的高度" -> `currentY >= headerHeight`.
224
+
225
+ // Why? If `bottom: 0`, it sits ON the bottom edge. That seems fine?
226
+ // Maybe they want it to float *above* something?
227
+
228
+ // Let's stick to the literal request:
229
+ // If isCollapsed && y === 'bottom', ensure style.bottom >= headerHeight.
230
+
231
+ // Header height is 32px (normal) or 8px (mini).
232
+ const headerHeight = props.mini ? 8 : 32;
233
+
234
+ // If user requests this constraint:
235
+ // We check if we are collapsed.
236
+ // If so, we use Math.max(currentY.value, headerHeight) ?
237
+ // Or just ensure it doesn't look weird.
238
+
239
+ // Let's try: if collapsed, force bottom offset to be at least 0? (It already is).
240
+ // If the user drags it down, currentY decreases.
241
+
242
+ // Interpretation B:
243
+ // Maybe the user wants the collapsed item to "stick" up a bit more?
244
+ // Let's apply: effectiveBottom = isCollapsed ? Math.max(currentY.value, 0) : currentY.value.
245
+ // Actually, let's look at `onResizeMove`.
246
+ // When resizing from bottom (y='bottom'), we change `currentY`.
247
+
248
+ // If the request is about "When I click collapse, ensure it doesn't jump off screen or get covered".
249
+
250
+ // Let's apply the requested constraint literally:
251
+ // if (isCollapsed && y === 'bottom') -> style.bottom = max(currentY, headerHeight)
252
+
253
+ let effectiveBottom = currentY.value;
254
+ if (isCollapsed.value) {
255
+ // Constrain bottom offset when collapsed?
256
+ // This seems unusual unless they want it to act like a docked bar.
257
+ // Let's assume they want to ensure visibility.
258
+ // But if I put it at bottom: 0, it's visible.
259
+
260
+ // Maybe they mean "The top of the component shouldn't go below the parent bottom"?
261
+ // For a bottom-anchored element:
262
+ // Top = ParentHeight - BottomOffset - Height.
263
+ // If Collapsed, Height = 32.
264
+ // Top = ParentHeight - BottomOffset - 32.
265
+ // If BottomOffset = 0 -> Top = ParentHeight - 32. (Visible at bottom).
266
+
267
+ // If they mean "bottom value" (the variable `currentY`), then:
268
+ // style.bottom = currentY.
269
+
270
+ // Let's re-read carefully: "收起时bottom的值不能小于组件标题栏的高度"
271
+ // "When collapsed, the bottom value cannot be less than the component header height."
272
+ // If headerHeight is 32. They want bottom >= 32.
273
+ // This would make it float 32px *above* the bottom edge.
274
+ // This prevents it from being flush with the bottom edge?
275
+
276
+ // Let's implement it as requested.
277
+ const minBottom = props.mini ? 8 : 32;
278
+ if (effectiveBottom < minBottom) {
279
+ // But we don't want to permanently change currentY, just the display style?
280
+ // Or should we clamp currentY?
281
+ // If we clamp style, dragging might feel weird.
282
+ // Let's just clamp the style value.
283
+ effectiveBottom = Math.max(effectiveBottom, minBottom);
284
+ }
285
+ }
286
+
287
+ style.bottom = `${effectiveBottom}px`;
288
+ style.top = "auto";
289
+ }
290
+
291
+ return style;
292
+ });
293
+
294
+ const toggleCollapse = () => {
295
+ isCollapsed.value = !isCollapsed.value;
296
+ };
297
+
298
+ const close = () => {
299
+ emit("close");
300
+ };
301
+
302
+ // Dragging Logic
303
+ const onHeaderMouseDown = (e: MouseEvent) => {
304
+ e.preventDefault();
305
+
306
+ isDragging.value = true;
307
+ const startX = e.clientX;
308
+ const startY = e.clientY;
309
+ const startOffsetX = currentX.value;
310
+ const startOffsetY = currentY.value;
311
+
312
+ const onMouseMove = (moveEvent: MouseEvent) => {
313
+ const deltaX = moveEvent.clientX - startX;
314
+ const deltaY = moveEvent.clientY - startY;
315
+
316
+ // Calculate new X offset based on alignment
317
+ if (props.x === "left") {
318
+ // Dragging right increases left offset
319
+ currentX.value = Math.max(0, startOffsetX + deltaX);
320
+ } else {
321
+ // Dragging right decreases right offset
322
+ currentX.value = Math.max(0, startOffsetX - deltaX);
323
+ }
324
+
325
+ // Calculate new Y offset based on alignment
326
+ if (props.y === "top") {
327
+ // Dragging down increases top offset
328
+ currentY.value = Math.max(0, startOffsetY + deltaY);
329
+ } else {
330
+ // Dragging down decreases bottom offset
331
+ currentY.value = Math.max(0, startOffsetY - deltaY);
332
+ }
333
+ };
334
+
335
+ const onMouseUp = () => {
336
+ isDragging.value = false;
337
+ document.removeEventListener("mousemove", onMouseMove);
338
+ document.removeEventListener("mouseup", onMouseUp);
339
+ };
340
+
341
+ document.addEventListener("mousemove", onMouseMove);
342
+ document.addEventListener("mouseup", onMouseUp);
343
+ };
344
+
345
+ // Resizing Logic
346
+ const onResizeMouseDown = (e: MouseEvent) => {
347
+ e.preventDefault();
348
+ e.stopPropagation();
349
+
350
+ isResizing.value = true;
351
+ const startY = e.clientY;
352
+
353
+ // Get initial VISUAL height in pixels (what the user sees)
354
+ const visualHeight = itemRef.value ? itemRef.value.offsetHeight : 0;
355
+
356
+ // We need to work with the "logical" height that matches our data model.
357
+ // Computed Style Logic: height = currentHeight + offset
358
+ // So: currentHeight = visualHeight - offset
359
+
360
+ const startLogicalHeight = visualHeight - props.heightOffset;
361
+ const startOffsetY = currentY.value;
362
+
363
+ const onResizeMove = (moveEvent: MouseEvent) => {
364
+ const deltaY = moveEvent.clientY - startY;
365
+
366
+ let newLogicalHeight = startLogicalHeight;
367
+ let newOffsetY = startOffsetY;
368
+
369
+ if (props.y === "top") {
370
+ // Dragging down increases height
371
+ // newLogicalHeight should be startLogicalHeight + deltaY
372
+ newLogicalHeight = Math.max(
373
+ 100 - props.heightOffset,
374
+ startLogicalHeight + deltaY
375
+ );
376
+ } else {
377
+ // y="bottom"
378
+ // Drag down (deltaY > 0) -> Increase Height, Decrease Offset
379
+ const potentialOffsetY = startOffsetY - deltaY;
380
+
381
+ if (potentialOffsetY >= 0) {
382
+ // We increase the logical height by deltaY
383
+ newLogicalHeight = Math.max(
384
+ 100 - props.heightOffset,
385
+ startLogicalHeight + deltaY
386
+ );
387
+ newOffsetY = potentialOffsetY;
388
+ } else {
389
+ const cappedDelta = startOffsetY;
390
+ // If we hit the bottom of screen (offset 0), we can only grow by the amount of startOffsetY
391
+ newLogicalHeight = Math.max(
392
+ 100 - props.heightOffset,
393
+ startLogicalHeight + cappedDelta
394
+ );
395
+ newOffsetY = 0;
396
+ }
397
+ }
398
+
399
+ // CRITICAL FIX: Ensure we are setting a NUMBER, not a string, to engage the 'number' branch of itemStyle
400
+ currentHeight.value = newLogicalHeight;
401
+
402
+ if (props.y === "bottom") {
403
+ currentY.value = newOffsetY;
404
+ }
405
+ };
406
+
407
+ const onResizeUp = () => {
408
+ isResizing.value = false;
409
+ document.removeEventListener("mousemove", onResizeMove);
410
+ document.removeEventListener("mouseup", onResizeUp);
411
+ };
412
+
413
+ document.addEventListener("mousemove", onResizeMove);
414
+ document.addEventListener("mouseup", onResizeUp);
415
+ };
416
+
417
+ // Watch for prop changes to reset position if alignment changes (optional but good)
418
+ watch(
419
+ () => [props.x, props.y],
420
+ () => {
421
+ currentX.value = props.xOffset;
422
+ currentY.value = props.yOffset;
423
+ }
424
+ );
425
+
426
+ watch(
427
+ () => props.height,
428
+ (val) => {
429
+ currentHeight.value = val;
430
+ }
431
+ );
432
+ </script>
433
+
434
+ <style lang="scss" scoped>
435
+ .mt-floating-panel {
436
+ display: flex;
437
+ flex-direction: column;
438
+ overflow: hidden;
439
+ background: #fff;
440
+ border: 1px solid #e0e0e0;
441
+ border-radius: 4px;
442
+ box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
443
+ transition:
444
+ height 0s,
445
+ width 0s;
446
+
447
+ &.is-interacting {
448
+ user-select: none;
449
+ transition: none !important;
450
+ }
451
+
452
+ &.is-collapsed {
453
+ // Height handled by inline style 'auto'
454
+ .mt-float-content {
455
+ display: none;
456
+ }
457
+ }
458
+
459
+ &.is-mini {
460
+ .mt-float-header {
461
+ height: 8px;
462
+ min-height: 8px;
463
+ padding: 0;
464
+ background: #e0e0e0;
465
+ border-bottom: none;
466
+
467
+ &:hover {
468
+ background: #d0d0d0;
469
+ }
470
+ }
471
+
472
+ .mt-float-content {
473
+ border-top: none;
474
+ }
475
+ }
476
+
477
+ .mt-float-header {
478
+ display: flex;
479
+ align-items: center;
480
+ justify-content: space-between;
481
+ height: 32px;
482
+ padding: 0 8px;
483
+ cursor: move;
484
+ user-select: none;
485
+ background: #f0f0f0;
486
+ border-bottom: 1px solid #e0e0e0;
487
+
488
+ .mt-header-left {
489
+ display: flex;
490
+ align-items: center;
491
+ overflow: hidden;
492
+
493
+ .mt-icon {
494
+ display: flex;
495
+ align-items: center;
496
+ margin-right: 6px;
497
+ color: #666;
498
+ }
499
+
500
+ .mt-title {
501
+ overflow: hidden;
502
+ font-size: 13px;
503
+ font-weight: 600;
504
+ color: #333;
505
+ text-overflow: ellipsis;
506
+ white-space: nowrap;
507
+ }
508
+ }
509
+
510
+ .mt-header-right {
511
+ display: flex;
512
+ align-items: center;
513
+
514
+ .mt-icon-btn {
515
+ display: flex;
516
+ align-items: center;
517
+ justify-content: center;
518
+ padding: 4px;
519
+ margin-left: 2px;
520
+ color: #666;
521
+ cursor: pointer;
522
+ background: transparent;
523
+ border: none;
524
+ border-radius: 4px;
525
+
526
+ &:hover {
527
+ color: #333;
528
+ background-color: rgb(0 0 0 / 5%);
529
+ }
530
+
531
+ .arrow {
532
+ transition: transform 0.2s;
533
+
534
+ &.rotated {
535
+ transform: rotate(-90deg);
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+
542
+ .mt-float-content {
543
+ position: relative;
544
+ flex: 1;
545
+ padding: 8px;
546
+ overflow: auto;
547
+
548
+ /* Custom Scrollbar */
549
+ &::-webkit-scrollbar {
550
+ width: 6px;
551
+ height: 6px;
552
+ }
553
+
554
+ &::-webkit-scrollbar-track {
555
+ background: transparent;
556
+ }
557
+
558
+ &::-webkit-scrollbar-thumb {
559
+ background: #ccc;
560
+ border-radius: 3px;
561
+ }
562
+
563
+ &::-webkit-scrollbar-thumb:hover {
564
+ background: #999;
565
+ }
566
+ }
567
+
568
+ .mt-resize-handle {
569
+ height: 6px;
570
+ cursor: ns-resize;
571
+ background-color: transparent;
572
+ transition: background-color 0.2s;
573
+
574
+ &:hover {
575
+ background-color: #1890ff;
576
+ }
577
+ }
578
+ }
579
+
580
+ :deep(.mt-header-right *) {
581
+ outline: none;
582
+ }
583
+ </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <div
3
+ class="mt-layout"
4
+ :class="[`mt-layout-${direction}`, { 'mt-layout-root': !isNested }]"
5
+ ref="layoutRef"
6
+ >
7
+ <slot></slot>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { provide, ref, toRef, reactive, readonly, inject } from "vue";
13
+
14
+ const props = defineProps({
15
+ direction: {
16
+ type: String as () => "horizontal" | "vertical",
17
+ default: "horizontal",
18
+ },
19
+ });
20
+
21
+ const layoutRef = ref<HTMLElement | null>(null);
22
+ const items = reactive<any[]>([]);
23
+ const parentLayout = inject<any>("mt-layout", null);
24
+ const isNested = !!parentLayout;
25
+
26
+ const addItem = (item: any) => {
27
+ items.push(item);
28
+ };
29
+
30
+ const removeItem = (id: string) => {
31
+ const index = items.findIndex((i) => i.id === id);
32
+ if (index > -1) {
33
+ items.splice(index, 1);
34
+ }
35
+ };
36
+
37
+ // Check if an item is the last one
38
+ const isLastItem = (id: string) => {
39
+ if (items.length === 0) return true;
40
+ return items[items.length - 1].id === id;
41
+ };
42
+
43
+ // Handle resize
44
+ // The splitter is associated with the item BEFORE it.
45
+ // Dragging the splitter changes the size of the item BEFORE it and the item AFTER it (or pushes others).
46
+ // For simplicity, let's assume flexbox.
47
+ // If we use flex-grow, resizing is tricky.
48
+ // Better to use explicit width/height percentages or pixels.
49
+ // Let's use flex-basis or explicit width/height.
50
+
51
+ const resizeItem = (id: string, delta: number) => {
52
+ const index = items.findIndex((i) => i.id === id);
53
+ if (index === -1 || index === items.length - 1) return;
54
+
55
+ const currentItem = items[index];
56
+ // We don't necessarily need to adjust the next item if we are using flexbox and total width.
57
+ // But usually we want to shrink the next item.
58
+
59
+ // Implementation detail: we will call a method on the item to request a size change.
60
+ // Or simpler: the items manage their own DOM size via styles, but the logic is here.
61
+
62
+ // Let's rely on the items to expose a resize method.
63
+ if (props.direction === "horizontal") {
64
+ currentItem.resize(delta);
65
+ } else {
66
+ currentItem.resize(delta);
67
+ }
68
+ };
69
+
70
+ provide("mt-layout", {
71
+ direction: toRef(props, "direction"),
72
+ addItem,
73
+ removeItem,
74
+ isLastItem,
75
+ resizeItem,
76
+ items: readonly(items),
77
+ });
78
+
79
+ defineExpose({
80
+ layoutRef,
81
+ });
82
+ </script>
83
+
84
+ <style lang="scss" scoped>
85
+ .mt-layout {
86
+ display: flex;
87
+ width: 100%;
88
+ height: 100%;
89
+ overflow: hidden;
90
+
91
+ &.mt-layout-horizontal {
92
+ flex-direction: row;
93
+ }
94
+
95
+ &.mt-layout-vertical {
96
+ flex-direction: column;
97
+ }
98
+ }
99
+ </style>