@m3ui-vue/m3ui-vue 0.4.7 → 0.5.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 (50) hide show
  1. package/dist/{MMenuItem-COdgD4m7.js → MMenuItem-Dw2ARZic.js} +30 -24
  2. package/dist/{MMenuItem-COdgD4m7.js.map → MMenuItem-Dw2ARZic.js.map} +1 -1
  3. package/dist/components/MAutocomplete.vue.d.ts +2 -1
  4. package/dist/components/MColorPicker.vue.d.ts +2 -1
  5. package/dist/components/MContextMenu.vue.d.ts +7 -21
  6. package/dist/components/MDatePicker.vue.d.ts +2 -1
  7. package/dist/components/MDateRangePicker.vue.d.ts +2 -1
  8. package/dist/components/MMaskField.vue.d.ts +2 -1
  9. package/dist/components/MMenuDivider.vue.d.ts +3 -0
  10. package/dist/components/MMenuItem.vue.d.ts +1 -0
  11. package/dist/components/MMultiAutocomplete.vue.d.ts +2 -1
  12. package/dist/components/MMultiSelect.vue.d.ts +2 -1
  13. package/dist/components/MNumberField.vue.d.ts +2 -1
  14. package/dist/components/MSelect.vue.d.ts +2 -1
  15. package/dist/components/MTagInput.vue.d.ts +2 -1
  16. package/dist/components/MTextField.vue.d.ts +2 -1
  17. package/dist/components/MTimePicker.vue.d.ts +2 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/m3ui-vue.css +1 -1
  20. package/dist/m3ui.js +819 -884
  21. package/dist/m3ui.js.map +1 -1
  22. package/dist/rich-text-editor.js +1 -1
  23. package/dist/styles.css +1 -1
  24. package/package.json +1 -1
  25. package/src/components/MAbsolute.vue +3 -2
  26. package/src/components/MAutocomplete.vue +3 -2
  27. package/src/components/MCircleProgressBar.vue +7 -5
  28. package/src/components/MColorPicker.vue +4 -2
  29. package/src/components/MColorPickerModal.vue +1 -0
  30. package/src/components/MContextMenu.vue +57 -38
  31. package/src/components/MDatePicker.vue +3 -2
  32. package/src/components/MDateRangePicker.vue +3 -2
  33. package/src/components/MMaskField.vue +3 -2
  34. package/src/components/MMenuDivider.vue +3 -0
  35. package/src/components/MMenuItem.vue +2 -0
  36. package/src/components/MMultiAutocomplete.vue +3 -2
  37. package/src/components/MMultiSelect.vue +3 -2
  38. package/src/components/MNotificationHost.vue +19 -5
  39. package/src/components/MNumberField.vue +3 -2
  40. package/src/components/MSelect.vue +3 -2
  41. package/src/components/MSlider.vue +20 -16
  42. package/src/components/MSnackbar.vue +15 -5
  43. package/src/components/MTabs.vue +1 -1
  44. package/src/components/MTagInput.vue +3 -2
  45. package/src/components/MTextField.vue +3 -2
  46. package/src/components/MTimePicker.vue +3 -2
  47. package/src/components/MWindow.vue +5 -5
  48. package/src/index.ts +1 -1
  49. package/dist/components/_MContextMenuPanel.vue.d.ts +0 -13
  50. package/src/components/_MContextMenuPanel.vue +0 -137
@@ -37,7 +37,7 @@ const props = withDefaults(
37
37
  const emit = defineEmits<{ 'update:modelValue': [number | [number, number]] }>()
38
38
 
39
39
  const trackEl = ref<HTMLElement>()
40
- const dragging = ref<false | 'single' | 'lo' | 'hi'>(false)
40
+ const dragging = ref<false | 'single' | 0 | 1>(false)
41
41
  const isVertical = computed(() => props.orientation === 'vertical')
42
42
  const isRange = computed(() => props.variant === 'range')
43
43
  const isCentered = computed(() => props.variant === 'centered')
@@ -100,11 +100,14 @@ function onPointerDown(e: PointerEvent) {
100
100
  e.preventDefault()
101
101
  const v = valueFromEvent(e)
102
102
  if (isRange.value) {
103
- const distLo = Math.abs(v - val.value.lo)
104
- const distHi = Math.abs(v - val.value.hi)
105
- dragging.value = distLo <= distHi ? 'lo' : 'hi'
106
103
  const cur = props.modelValue as [number, number]
107
- emit('update:modelValue', dragging.value === 'lo' ? [v, cur[1]] : [cur[0], v])
104
+ const dist0 = Math.abs(v - cur[0])
105
+ const dist1 = Math.abs(v - cur[1])
106
+ const idx: 0 | 1 = dist0 <= dist1 ? 0 : 1
107
+ dragging.value = idx
108
+ const next: [number, number] = [cur[0], cur[1]]
109
+ next[idx] = v
110
+ emit('update:modelValue', next)
108
111
  } else {
109
112
  dragging.value = 'single'
110
113
  emit('update:modelValue', v)
@@ -115,11 +118,13 @@ function onPointerDown(e: PointerEvent) {
115
118
  }
116
119
 
117
120
  function onPointerMove(e: PointerEvent) {
118
- if (!dragging.value) return
121
+ if (dragging.value === false) return
119
122
  const v = valueFromEvent(e)
120
123
  if (isRange.value) {
121
124
  const cur = props.modelValue as [number, number]
122
- emit('update:modelValue', dragging.value === 'lo' ? [v, cur[1]] : [cur[0], v])
125
+ const next: [number, number] = [cur[0], cur[1]]
126
+ next[dragging.value as 0 | 1] = v
127
+ emit('update:modelValue', next)
123
128
  } else {
124
129
  emit('update:modelValue', v)
125
130
  }
@@ -169,7 +174,7 @@ const stopPositions = computed(() => {
169
174
  })
170
175
 
171
176
  const r = computed(() => s.value.radius)
172
- const nd = computed(() => !dragging.value)
177
+ const nd = computed(() => dragging.value === false)
173
178
  const tr = computed(() => nd.value ? '75ms ease' : '0s')
174
179
 
175
180
  function thumbPos(pct: number) {
@@ -188,11 +193,11 @@ function thumbPos(pct: number) {
188
193
  if (isVertical.value) {
189
194
  base.left = '50%'
190
195
  base.bottom = `${pct}%`
191
- base.transform = `translateX(-50%) translateY(50%) scaleX(${dragging.value ? 1.08 : 1})`
196
+ base.transform = `translateX(-50%) translateY(50%) scaleX(${dragging.value !== false ? 1.08 : 1})`
192
197
  } else {
193
198
  base.left = `${pct}%`
194
199
  base.top = '50%'
195
- base.transform = `translateX(-50%) translateY(-50%) scaleY(${dragging.value ? 1.08 : 1})`
200
+ base.transform = `translateX(-50%) translateY(-50%) scaleY(${dragging.value !== false ? 1.08 : 1})`
196
201
  }
197
202
  return base
198
203
  }
@@ -212,15 +217,14 @@ const displayValue = computed(() => {
212
217
  })
213
218
 
214
219
  const tooltipValue = computed(() => {
215
- if (isRange.value) {
216
- if (dragging.value === 'lo') return val.value.lo
217
- return val.value.hi
218
- }
220
+ if (isRange.value && typeof dragging.value === 'number')
221
+ return (props.modelValue as [number, number])[dragging.value]
219
222
  return props.modelValue
220
223
  })
221
224
 
222
225
  const tooltipPct = computed(() => {
223
- if (isRange.value) return dragging.value === 'lo' ? pctLo.value : pctHi.value
226
+ if (isRange.value && typeof dragging.value === 'number')
227
+ return toPct((props.modelValue as [number, number])[dragging.value])
224
228
  return pctLo.value
225
229
  })
226
230
  </script>
@@ -408,7 +412,7 @@ const tooltipPct = computed(() => {
408
412
  leave-to-class="opacity-0"
409
413
  >
410
414
  <div
411
- v-if="showTooltip && dragging"
415
+ v-if="showTooltip && dragging !== false"
412
416
  class="pointer-events-none absolute z-10 flex h-7 min-w-7 items-center justify-center rounded-full bg-inverse-surface px-2 text-label-small tabular-nums text-inverse-on-surface"
413
417
  :style="tooltipPos(tooltipPct)"
414
418
  >
@@ -94,7 +94,7 @@ const getVariantStyle = (variant: string): VariantStyle =>
94
94
  <Transition name="m3-badge">
95
95
  <span
96
96
  v-if="t.count >= 2"
97
- class="absolute -top-1.5 -right-1.5 z-10 flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-on-primary shadow-elevation-1"
97
+ class="toast-count-badge absolute -top-1.5 -right-1.5 z-10 flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-on-primary shadow-elevation-1"
98
98
  >
99
99
  <Transition name="m3-count" mode="out-in">
100
100
  <span :key="t.count">{{ t.count }}</span>
@@ -178,7 +178,6 @@ const getVariantStyle = (variant: string): VariantStyle =>
178
178
  transition:
179
179
  grid-template-rows 220ms cubic-bezier(0.2, 0, 0, 1),
180
180
  padding-bottom 220ms cubic-bezier(0.2, 0, 0, 1);
181
- overflow: hidden;
182
181
  }
183
182
  .m3-toast-bot-enter-active > .toast-inner {
184
183
  transition:
@@ -198,7 +197,6 @@ const getVariantStyle = (variant: string): VariantStyle =>
198
197
  transition:
199
198
  grid-template-rows 300ms cubic-bezier(0.2, 0, 0, 1),
200
199
  padding-bottom 300ms cubic-bezier(0.2, 0, 0, 1);
201
- overflow: hidden;
202
200
  }
203
201
  .m3-toast-bot-leave-active > .toast-inner {
204
202
  transition:
@@ -213,13 +211,19 @@ const getVariantStyle = (variant: string): VariantStyle =>
213
211
  opacity: 0;
214
212
  transform: scale(0.92);
215
213
  }
214
+ .m3-toast-bot-leave-active > .toast-count-badge {
215
+ transition: opacity 150ms ease, transform 150ms ease;
216
+ }
217
+ .m3-toast-bot-leave-to > .toast-count-badge {
218
+ opacity: 0;
219
+ transform: scale(0.5);
220
+ }
216
221
 
217
222
  /* ─── Top toasts ────────────────────────────────────────────────── */
218
223
  .m3-toast-top-enter-active {
219
224
  transition:
220
225
  grid-template-rows 220ms cubic-bezier(0.2, 0, 0, 1),
221
226
  padding-bottom 220ms cubic-bezier(0.2, 0, 0, 1);
222
- overflow: hidden;
223
227
  }
224
228
  .m3-toast-top-enter-active > .toast-inner {
225
229
  transition:
@@ -239,7 +243,6 @@ const getVariantStyle = (variant: string): VariantStyle =>
239
243
  transition:
240
244
  grid-template-rows 300ms cubic-bezier(0.2, 0, 0, 1),
241
245
  padding-bottom 300ms cubic-bezier(0.2, 0, 0, 1);
242
- overflow: hidden;
243
246
  }
244
247
  .m3-toast-top-leave-active > .toast-inner {
245
248
  transition:
@@ -254,6 +257,13 @@ const getVariantStyle = (variant: string): VariantStyle =>
254
257
  opacity: 0;
255
258
  transform: scale(0.92);
256
259
  }
260
+ .m3-toast-top-leave-active > .toast-count-badge {
261
+ transition: opacity 150ms ease, transform 150ms ease;
262
+ }
263
+ .m3-toast-top-leave-to > .toast-count-badge {
264
+ opacity: 0;
265
+ transform: scale(0.5);
266
+ }
257
267
 
258
268
  /* badge appear/disappear */
259
269
  .m3-badge-enter-active { transition: opacity 0.2s ease, transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); }
@@ -103,7 +103,7 @@ function select(tab: Tab) {
103
103
  width: `${indicatorWidth}px`,
104
104
  transition: stretching
105
105
  ? 'left 150ms cubic-bezier(0.4, 0, 0.2, 1), width 150ms cubic-bezier(0.4, 0, 0.2, 1)'
106
- : 'left 250ms cubic-bezier(0.34, 1.56, 0.64, 1), width 250ms cubic-bezier(0.34, 1.56, 0.64, 1)',
106
+ : 'left 180ms cubic-bezier(0.34, 1.4, 0.64, 1), width 180ms cubic-bezier(0.34, 1.4, 0.64, 1)',
107
107
  }"
108
108
  />
109
109
  </div>
@@ -11,7 +11,8 @@ const props = withDefaults(
11
11
  placeholder?: string
12
12
  variant?: 'filled' | 'outlined'
13
13
  disabled?: boolean
14
- error?: string
14
+ error?: boolean
15
+ errorLabel?: string
15
16
  hint?: string
16
17
  required?: boolean
17
18
  leadingIcon?: string
@@ -235,7 +236,7 @@ const labelClasses = computed(() => {
235
236
  </button>
236
237
  </div>
237
238
 
238
- <p v-if="error" class="px-4 text-body-small text-error">{{ error }}</p>
239
+ <p v-if="error && errorLabel" class="px-4 text-body-small text-error">{{ errorLabel }}</p>
239
240
  <p v-else-if="hint" class="px-4 text-body-small text-on-surface-variant">{{ hint }}</p>
240
241
  </div>
241
242
  </template>
@@ -10,7 +10,8 @@ const props = withDefaults(
10
10
  label: string;
11
11
  type?: string;
12
12
  variant?: "filled" | "outlined";
13
- error?: string;
13
+ error?: boolean;
14
+ errorLabel?: string;
14
15
  hint?: string;
15
16
  disabled?: boolean;
16
17
  required?: boolean;
@@ -188,7 +189,7 @@ function onInput(event: Event) {
188
189
  </button>
189
190
  </div>
190
191
 
191
- <p v-if="error" class="px-4 text-body-small text-error">{{ error }}</p>
192
+ <p v-if="error && errorLabel" class="px-4 text-body-small text-error">{{ errorLabel }}</p>
192
193
  <p v-else-if="hint" class="px-4 text-body-small text-on-surface-variant">{{ hint }}</p>
193
194
  </div>
194
195
  </template>
@@ -9,7 +9,8 @@ const props = withDefaults(defineProps<{
9
9
  label?: string
10
10
  placeholder?: string
11
11
  disabled?: boolean
12
- error?: string
12
+ error?: boolean
13
+ errorLabel?: string
13
14
  hint?: string
14
15
  minuteStep?: number
15
16
  use24h?: boolean
@@ -148,7 +149,7 @@ onUnmounted(() => {
148
149
  </label>
149
150
  </div>
150
151
 
151
- <p v-if="error" class="px-4 text-body-small text-error">{{ error }}</p>
152
+ <p v-if="error && errorLabel" class="px-4 text-body-small text-error">{{ errorLabel }}</p>
152
153
  <p v-else-if="hint" class="px-4 text-body-small text-on-surface-variant">{{ hint }}</p>
153
154
 
154
155
  <Teleport to="body">
@@ -257,11 +257,11 @@ onUnmounted(() => {
257
257
 
258
258
  // Resize handle definitions
259
259
  const resizeHandles: { dir: ResizeDirection; class: string; cursor: string }[] = [
260
- { dir: 's', class: 'bottom-0 left-[10px] right-[10px] h-[5px]', cursor: 'cursor-ns-resize' },
261
- { dir: 'e', class: 'top-[10px] right-0 bottom-[10px] w-[5px]', cursor: 'cursor-ew-resize' },
262
- { dir: 'w', class: 'top-[10px] left-0 bottom-[10px] w-[5px]', cursor: 'cursor-ew-resize' },
263
- { dir: 'se', class: 'bottom-0 right-0 w-[12px] h-[12px]', cursor: 'cursor-nwse-resize' },
264
- { dir: 'sw', class: 'bottom-0 left-0 w-[12px] h-[12px]', cursor: 'cursor-nesw-resize' },
260
+ { dir: 's', class: 'bottom-0 left-[20px] right-[20px] h-[8px]', cursor: 'cursor-ns-resize' },
261
+ { dir: 'e', class: 'top-[20px] right-0 bottom-[20px] w-[8px]', cursor: 'cursor-ew-resize' },
262
+ { dir: 'w', class: 'top-[20px] left-0 bottom-[20px] w-[8px]', cursor: 'cursor-ew-resize' },
263
+ { dir: 'se', class: 'bottom-0 right-0 w-[20px] h-[20px]', cursor: 'cursor-nwse-resize' },
264
+ { dir: 'sw', class: 'bottom-0 left-0 w-[20px] h-[20px]', cursor: 'cursor-nesw-resize' },
265
265
  ]
266
266
  </script>
267
267
 
package/src/index.ts CHANGED
@@ -81,6 +81,7 @@ export { default as MLoadingOverlay } from './components/MLoadingOverlay.vue'
81
81
  // MMarkdown — import from '@m3ui-vue/m3ui-vue/markdown'
82
82
  export { default as MMasonry } from './components/MMasonry.vue'
83
83
  export { default as MMenu } from './components/MMenu.vue'
84
+ export { default as MMenuDivider } from './components/MMenuDivider.vue'
84
85
  export { default as MMenuItem } from './components/MMenuItem.vue'
85
86
  export { default as MMaskField } from './components/MMaskField.vue'
86
87
  export type { MaskPreset } from './components/MMaskField.vue'
@@ -151,7 +152,6 @@ export type { Placement } from './components/MAbsolute.vue'
151
152
  export type { BadgePosition, BadgeOverlap } from './components/MBadge.vue'
152
153
  export type { CalendarEvent } from './components/MCalendar.vue'
153
154
  export type { CarouselItem } from './components/MCarousel.vue'
154
- export type { ContextMenuItem } from './components/MContextMenu.vue'
155
155
  export type { DataTableColumn, DataTableFetchParams } from './components/MDataTable.vue'
156
156
  export type { DateRange } from './components/MDateRangePicker.vue'
157
157
  export type { DragDropItem } from './components/MDragDropList.vue'
@@ -1,13 +0,0 @@
1
- import type { ContextMenuItem } from "./MContextMenu.vue";
2
- type __VLS_Props = {
3
- items: ContextMenuItem[];
4
- x: number;
5
- y: number;
6
- };
7
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
- close: () => any;
9
- }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
- onClose?: (() => any) | undefined;
11
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
- declare const _default: typeof __VLS_export;
13
- export default _default;
@@ -1,137 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, onMounted, nextTick } from "vue";
3
- import MIcon from "./MIcon.vue";
4
- import MContextMenuPanel from "./_MContextMenuPanel.vue";
5
- import type { ContextMenuItem } from "./MContextMenu.vue";
6
-
7
- const props = defineProps<{
8
- items: ContextMenuItem[];
9
- x: number;
10
- y: number;
11
- }>();
12
-
13
- const emit = defineEmits<{ close: [] }>();
14
-
15
- const panel = ref<HTMLElement | null>(null);
16
- const panelX = ref(props.x);
17
- const panelY = ref(props.y);
18
- const activeIndex = ref<number | null>(null);
19
- const subPos = ref({ x: 0, y: 0 });
20
-
21
- onMounted(async () => {
22
- await nextTick();
23
- if (!panel.value) return;
24
- const el = panel.value;
25
- panelX.value = Math.min(props.x, window.innerWidth - el.offsetWidth - 8);
26
- panelY.value = Math.min(props.y, window.innerHeight - el.offsetHeight - 8);
27
- });
28
-
29
- function onItemMouseEnter(index: number, item: ContextMenuItem, e: MouseEvent) {
30
- if (item.divider || item.disabled) {
31
- activeIndex.value = null;
32
- return;
33
- }
34
- if (!item.children?.length) {
35
- activeIndex.value = null;
36
- return;
37
- }
38
-
39
- activeIndex.value = index;
40
- const itemEl = e.currentTarget as HTMLElement;
41
- const itemRect = itemEl.getBoundingClientRect();
42
- const panelRect = panel.value!.getBoundingClientRect();
43
-
44
- let x = panelRect.right;
45
- let y = itemRect.top;
46
- if (x + 220 > window.innerWidth) x = panelRect.left - 220;
47
- if (y + 300 > window.innerHeight) y = Math.max(8, window.innerHeight - 300);
48
-
49
- subPos.value = { x, y };
50
- }
51
-
52
- function onItemClick(item: ContextMenuItem) {
53
- if (item.disabled || item.divider || item.children?.length) return;
54
- item.onClick?.();
55
- emit("close");
56
- }
57
-
58
- function onPanelMouseLeave(e: MouseEvent) {
59
- // Don't close if the mouse moved to another context menu panel (sibling sub-panel)
60
- const related = e.relatedTarget as Element | null;
61
- if (related?.closest(".m3-ctx-panel")) return;
62
- activeIndex.value = null;
63
- }
64
- </script>
65
-
66
- <template>
67
- <div
68
- ref="panel"
69
- class="m3-ctx-panel absolute z-201 min-w-[200px] overflow-hidden rounded-lg bg-surface-container shadow-elevation-2"
70
- :style="{ left: `${panelX}px`, top: `${panelY}px` }"
71
- @mouseleave="onPanelMouseLeave"
72
- >
73
- <div class="py-1">
74
- <template v-for="(item, i) in items" :key="i">
75
- <hr v-if="item.divider" class="my-1 border-outline-variant" />
76
-
77
- <component
78
- v-else
79
- :is="item.to && !item.disabled ? 'RouterLink' : 'div'"
80
- :to="item.to && !item.disabled ? item.to : undefined"
81
- class="relative flex cursor-default select-none items-center gap-3 px-4 py-2.5 text-body-large"
82
- :class="[
83
- item.disabled
84
- ? 'cursor-not-allowed opacity-38 text-on-surface'
85
- : item.danger
86
- ? 'cursor-pointer text-error hover:bg-error/8'
87
- : 'cursor-pointer text-on-surface hover:bg-on-surface/8',
88
- activeIndex === i && !item.disabled
89
- ? item.danger
90
- ? 'bg-error/8'
91
- : 'bg-on-surface/8'
92
- : '',
93
- ]"
94
- @mouseenter="onItemMouseEnter(i, item, $event)"
95
- @click="onItemClick(item)"
96
- >
97
- <MIcon
98
- v-if="item.icon"
99
- :name="item.icon"
100
- :size="18"
101
- class="shrink-0"
102
- :class="item.danger ? 'text-error' : 'text-on-surface-variant'"
103
- />
104
- <span v-else class="w-[18px] shrink-0" />
105
-
106
- <span class="flex-1">{{ item.label }}</span>
107
-
108
- <span
109
- v-if="item.shortcut"
110
- class="text-label-small text-on-surface-variant"
111
- >
112
- {{ item.shortcut }}
113
- </span>
114
-
115
- <MIcon
116
- v-if="item.children?.length"
117
- name="chevron_right"
118
- :size="18"
119
- class="shrink-0 text-on-surface-variant"
120
- />
121
- </component>
122
- </template>
123
- </div>
124
- </div>
125
-
126
- <!-- Sub-panel: sibling in the same Teleport layer.
127
- No <Transition> wrapper — MContextMenuPanel is a fragment and cannot be
128
- animated by Vue's Transition (produces a console warning). -->
129
- <MContextMenuPanel
130
- v-if="activeIndex !== null && items[activeIndex]?.children?.length"
131
- :key="activeIndex"
132
- :items="items[activeIndex]!.children!"
133
- :x="subPos.x"
134
- :y="subPos.y"
135
- @close="emit('close')"
136
- />
137
- </template>