webcake-ui-kit 1.1.33 → 1.1.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-ui-kit",
3
- "version": "1.1.33",
3
+ "version": "1.1.35",
4
4
  "description": "UI Kit for Vue 2 && 3 - Pure Options API",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -25,10 +25,21 @@
25
25
  </div>
26
26
  <template #footer>
27
27
  <slot name="footer">
28
- <Button :variant="cancelVariant" class="ui-alert-dialog-btn" @click="handleCancel">{{ cancelText }}</Button>
29
- <Button :variant="okVariant" :loading="confirmLoading" class="ui-alert-dialog-btn" @click="handleOk">{{
30
- okText
31
- }}</Button>
28
+ <Button
29
+ size="sm"
30
+ :variant="cancelVariant"
31
+ class="ui-alert-dialog-btn"
32
+ @click="handleCancel"
33
+ :label="cancelText"
34
+ />
35
+ <Button
36
+ size="sm"
37
+ :variant="okVariant"
38
+ :loading="confirmLoading"
39
+ class="ui-alert-dialog-btn"
40
+ @click="handleOk"
41
+ :label="okText"
42
+ />
32
43
  </slot>
33
44
  </template>
34
45
  </Dialog>
@@ -11,6 +11,8 @@
11
11
  font-weight: var(--paragraph-bold-font-weight);
12
12
  white-space: nowrap;
13
13
  transition: box-shadow 0.15s ease;
14
+ border-width: 1px;
15
+ border-style: solid;
14
16
  }
15
17
 
16
18
  .ui-badge--round-default {
@@ -21,35 +23,39 @@
21
23
  }
22
24
 
23
25
  .ui-badge--primary {
24
- background: var(--primary-brand-bg);
25
- color: var(--inverse-fg);
26
+ background: var(--positive-subtle);
27
+ color: var(--primary-brand-fg);
28
+ border-color: var(--positive-border-subtle);
26
29
  }
27
30
  .ui-badge--secondary {
28
31
  background: var(--secondary-bg);
29
32
  color: var(--secondary-fg);
33
+ border: none;
30
34
  }
31
35
  .ui-badge--outline {
32
36
  background: var(--outline);
33
37
  color: var(--primary-fg);
34
- border-width: 1px;
35
- border-style: solid;
36
38
  border-color: var(--border-primary);
37
39
  }
38
40
  .ui-badge--ghost {
39
41
  background: var(--ghost);
40
42
  color: var(--primary-fg);
43
+ border: none;
41
44
  }
42
45
  .ui-badge--destructive {
43
- background: var(--destructive);
44
- color: var(--destructive-inverse-fg);
46
+ background: var(--destructive-subtle);
47
+ color: var(--destructive);
48
+ border-color: var(--destructive-border-subtle);
45
49
  }
46
50
  .ui-badge--info {
47
- background: var(--blue-500);
48
- color: var(--inverse-fg);
51
+ background: var(--infor-subtle);
52
+ color: var(--infor);
53
+ border-color: var(--infor-border-subtle);
49
54
  }
50
55
  .ui-badge--warning {
51
- background: var(--yellow-500);
52
- color: var(--inverse-fg);
56
+ background: var(--warning-subtle);
57
+ color: var(--warning);
58
+ border-color: var(--warning-border-subtle);
53
59
  }
54
60
 
55
61
  .ui-badge:focus-visible {
@@ -48,7 +48,8 @@ export default {
48
48
  variant: {
49
49
  type: String,
50
50
  default: 'primary',
51
- validator: v => ['primary', 'neutral', 'secondary', 'outline', 'ghost', 'destructive', 'link'].includes(v)
51
+ validator: v =>
52
+ ['primary', 'neutral', 'secondary', 'outline', 'ghost', 'destructive', 'link', 'ghost-destructive'].includes(v)
52
53
  },
53
54
  size: {
54
55
  type: String,
@@ -9,6 +9,7 @@
9
9
  cursor: pointer;
10
10
  font-family: var(--font-family-body);
11
11
  font-weight: var(--paragraph-medium-font-weight);
12
+ justify-content: center;
12
13
  text-align: center;
13
14
  white-space: nowrap;
14
15
  transition:
@@ -20,7 +21,6 @@
20
21
 
21
22
  .ui-btn__label {
22
23
  display: inline-flex;
23
- flex: 1;
24
24
  }
25
25
 
26
26
  .ui-btn__icon {
@@ -218,6 +218,17 @@
218
218
  background: var(--destructive-hover);
219
219
  }
220
220
 
221
+ /* ghost-destructive */
222
+ .ui-btn--ghost-destructive {
223
+ background: var(--ghost);
224
+ color: var(--destructive);
225
+ }
226
+
227
+ .ui-btn--ghost-destructive:hover,
228
+ .ui-btn--ghost-destructive:active {
229
+ background: var(--destructive-subtle);
230
+ }
231
+
221
232
  /* Link — text-only button on transparent ghost bg, brand-colored. */
222
233
  .ui-btn--link {
223
234
  background: var(--ghost);
@@ -252,7 +263,8 @@
252
263
  border-color: var(--border-focus);
253
264
  }
254
265
 
255
- .ui-btn--destructive:focus-visible {
266
+ .ui-btn--destructive:focus-visible,
267
+ .ui-btn--ghost-destructive:focus-visible {
256
268
  box-shadow: 0 0 0 3px var(--focus-ring-error);
257
269
  }
258
270
 
@@ -265,6 +277,13 @@
265
277
  pointer-events: none;
266
278
  }
267
279
 
280
+ .ui-btn:not(.ui-btn--ghost):not(.ui-btn--link):disabled {
281
+ background: var(--muted-bg);
282
+ color: var(--muted-fg);
283
+ border: 1px solid var(--border-primary);
284
+ box-shadow: var(--shadow-xs);
285
+ }
286
+
268
287
  /* ============================================================
269
288
  Icon-only — square button, uniform padding per size.
270
289
  Width = height; default slot content rendered in .ui-btn__icon
@@ -31,3 +31,7 @@
31
31
  position: relative;
32
32
  z-index: 1;
33
33
  }
34
+
35
+ .ui-button-group .ui-btn--outline:disabled {
36
+ background: var(--secondary-hover) !important;
37
+ }
@@ -127,7 +127,16 @@
127
127
  */
128
128
  import Menu from '../menu/Menu.vue'
129
129
  import CalendarPanel from './CalendarPanel.vue'
130
- import dayjs from './dayjs.js'
130
+ import dayjs from 'dayjs'
131
+ import customParseFormat from 'dayjs/plugin/customParseFormat'
132
+ import localeData from 'dayjs/plugin/localeData'
133
+ import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
134
+ import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
135
+
136
+ dayjs.extend(customParseFormat)
137
+ dayjs.extend(localeData)
138
+ dayjs.extend(isSameOrBefore)
139
+ dayjs.extend(isSameOrAfter)
131
140
  import { ISO, toDayjs, toISO, isDateDisabled, weekdayLabels, buildDayMatrix, orderISO } from './calendar.js'
132
141
  import { WkiCalendar, WkiX } from '../../icons'
133
142
 
@@ -6,7 +6,12 @@
6
6
  // its selection as ISO strings internally (stable identity, no deep-reactive
7
7
  // Day.js objects in `data`) and only converts to Date/formatted output at the
8
8
  // component boundary.
9
- import dayjs from './dayjs.js'
9
+ import dayjs from 'dayjs'
10
+ import customParseFormat from 'dayjs/plugin/customParseFormat'
11
+ import localeData from 'dayjs/plugin/localeData'
12
+
13
+ dayjs.extend(customParseFormat)
14
+ dayjs.extend(localeData)
10
15
 
11
16
  export const ISO = 'YYYY-MM-DD'
12
17
 
@@ -1,7 +1,7 @@
1
1
  .ui-time-panel {
2
2
  display: flex;
3
3
  gap: var(--spacing-3xs);
4
- height: 200px;
4
+ height: 220px;
5
5
  font-family: var(--font-family-body);
6
6
  }
7
7
 
@@ -81,8 +81,10 @@
81
81
  >
82
82
  <div class="ui-dialog-footer__inner">
83
83
  <slot name="footer">
84
- <Button :variant="cancelVariant" @click="handleCancel">{{ cancelText }}</Button>
85
- <Button :variant="okVariant" :loading="confirmLoading" @click="handleOk">{{ okText }}</Button>
84
+ <Button size="sm" :variant="cancelVariant" @click="handleCancel">{{ cancelText }}</Button>
85
+ <Button size="sm" :variant="okVariant" :loading="confirmLoading" @click="handleOk">{{
86
+ okText
87
+ }}</Button>
86
88
  </slot>
87
89
  </div>
88
90
  </div>
@@ -95,6 +95,7 @@
95
95
  /* Disabled — visual only; cursor handled by inner field. */
96
96
  .ui-input--disabled {
97
97
  opacity: 0.5;
98
+ background: var(--secondary-bg);
98
99
  }
99
100
 
100
101
  .ui-input--disabled:focus-within {
@@ -19,7 +19,6 @@
19
19
  box-shadow: var(--shadow-md);
20
20
  display: flex;
21
21
  flex-direction: column;
22
- min-width: 176px;
23
22
  outline: none;
24
23
  pointer-events: auto;
25
24
  box-sizing: border-box;
@@ -1,25 +1,40 @@
1
1
  <template>
2
2
  <div class="ui-message" :class="`ui-message--${type}`" role="status" aria-live="polite">
3
- <span class="ui-message__icon">
4
- <WkSpinner v-if="type === 'loading'" size="sm" />
5
- <component :is="iconComponent" v-else :size="18" />
6
- </span>
7
- <div class="ui-message__wrapper">
8
- <span class="ui-message__content">
9
- <slot>{{ content }}</slot>
3
+ <div class="ui-message__row">
4
+ <div v-if="imgSrc" class="ui-message__file-upload__preview">
5
+ <img :src="imgSrc" alt="" />
6
+ </div>
7
+ <span v-else class="ui-message__icon">
8
+ <WkSpinner v-if="type === 'loading'" size="sm" />
9
+ <component :is="iconComponent" v-else :size="18" />
10
10
  </span>
11
- <span v-if="description" class="ui-message__description">{{ description }}</span>
11
+ <div class="ui-message__wrapper">
12
+ <span class="ui-message__content">
13
+ <slot>{{ content }}</slot>
14
+ </span>
15
+ <span v-if="description" class="ui-message__description">{{ description }}</span>
16
+ </div>
17
+ <WkButton
18
+ v-if="action"
19
+ size="mini"
20
+ :variant="progress ? 'ghost' : action.variant || 'primary'"
21
+ @click="handleAction"
22
+ :label="action.label"
23
+ >
24
+ <template #icon>
25
+ <WkiPause />
26
+ </template>
27
+ </WkButton>
12
28
  </div>
13
- <WkButton v-if="action" size="mini" :variant="action.variant || 'primary'" @click="handleAction">{{
14
- action.label
15
- }}</WkButton>
29
+ <WkProgress v-if="progress !== null" :value="progress" />
16
30
  </div>
17
31
  </template>
18
32
 
19
33
  <script>
20
34
  import WkSpinner from '../spinner/Spinner.vue'
21
35
  import WkButton from '../button/Button.vue'
22
- import { WkiCircleCheck, WkiCircleX, WkiTriangleAlert, WkiInfo } from '../../icons'
36
+ import WkProgress from '../progress/Progress.vue'
37
+ import { WkiCircleCheck, WkiCircleX, WkiTriangleAlert, WkiInfo, WkiPause } from '../../icons'
23
38
 
24
39
  const ICON_BY_TYPE = {
25
40
  success: 'WkiCircleCheck',
@@ -30,7 +45,7 @@ const ICON_BY_TYPE = {
30
45
 
31
46
  export default {
32
47
  name: 'Message',
33
- components: { WkSpinner, WkButton, WkiCircleCheck, WkiCircleX, WkiTriangleAlert, WkiInfo },
48
+ components: { WkSpinner, WkButton, WkProgress, WkiCircleCheck, WkiCircleX, WkiTriangleAlert, WkiInfo, WkiPause },
34
49
  props: {
35
50
  type: {
36
51
  type: String,
@@ -50,6 +65,14 @@ export default {
50
65
  action: {
51
66
  type: Object,
52
67
  default: null
68
+ },
69
+ progress: {
70
+ type: Number,
71
+ default: null
72
+ },
73
+ imgSrc: {
74
+ type: String,
75
+ default: ''
53
76
  }
54
77
  },
55
78
  emits: ['close'],
@@ -13,6 +13,8 @@
13
13
  :content="item.content"
14
14
  :description="item.description"
15
15
  :action="item.action"
16
+ :progress="item.progress"
17
+ :img-src="item.imgSrc"
16
18
  @close="remove(item.key)"
17
19
  />
18
20
  </div>
@@ -145,7 +147,9 @@ export default {
145
147
  existing.content = config.content != null ? config.content : ''
146
148
  existing.description = config.description || null
147
149
  existing.action = config.action || null
148
- existing.onClose = config.onClose || null
150
+ ;((existing.progress = config.progress != null ? config.progress : null),
151
+ (existing.imgSrc = config.imgSrc),
152
+ (existing.onClose = config.onClose || null))
149
153
  this.scheduleRemoval(key, config.duration)
150
154
  return key
151
155
  }
@@ -155,6 +159,8 @@ export default {
155
159
  content: config.content != null ? config.content : '',
156
160
  description: config.description || null,
157
161
  action: config.action || null,
162
+ progress: config.progress != null ? config.progress : null,
163
+ imgSrc: config.imgSrc,
158
164
  onClose: config.onClose || null
159
165
  })
160
166
  // Enforce maxCount by evicting the oldest entries.
@@ -1,7 +1,7 @@
1
1
  .ui-message {
2
2
  pointer-events: auto;
3
3
  display: inline-flex;
4
- align-items: center;
4
+ flex-direction: column;
5
5
  gap: var(--spacing-sm);
6
6
  max-width: 480px;
7
7
  padding: var(--spacing-sm) var(--spacing-md);
@@ -11,6 +11,12 @@
11
11
  box-shadow: var(--shadow-sonner);
12
12
  }
13
13
 
14
+ .ui-message__row {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: var(--spacing-sm);
18
+ }
19
+
14
20
  .ui-message__icon {
15
21
  display: inline-flex;
16
22
  align-items: center;
@@ -35,6 +41,9 @@
35
41
  line-height: var(--paragraph-small-line-height);
36
42
  letter-spacing: var(--paragraph-small-letter-spacing, 0);
37
43
  word-break: break-word;
44
+ white-space: nowrap;
45
+ overflow: hidden;
46
+ text-overflow: ellipsis;
38
47
  }
39
48
 
40
49
  .ui-message__description {
@@ -46,6 +55,27 @@
46
55
  line-height: var(--paragraph-small-line-height);
47
56
  letter-spacing: var(--paragraph-small-letter-spacing);
48
57
  word-break: break-word;
58
+ -webkit-line-clamp: 2;
59
+ line-clamp: 2;
60
+ overflow: hidden;
61
+ }
62
+
63
+ .ui-message--file-upload {
64
+ width: 360px;
65
+ }
66
+
67
+ .ui-message__file-upload__preview {
68
+ width: 32px;
69
+ height: 32px;
70
+ border-radius: var(--rounded-sm);
71
+ overflow: hidden;
72
+ flex-shrink: 0;
73
+ }
74
+
75
+ .ui-message__file-upload__preview img {
76
+ width: 100%;
77
+ height: 100%;
78
+ object-fit: cover;
49
79
  }
50
80
 
51
81
  .ui-message--success .ui-message__icon {
@@ -87,6 +87,8 @@ function open(config) {
87
87
  content: config.content,
88
88
  description: config.description,
89
89
  action: config.action,
90
+ progress: config.progress != null ? config.progress : null,
91
+ imgSrc: config.imgSrc,
90
92
  duration,
91
93
  onClose: config.onClose
92
94
  })
@@ -176,7 +176,7 @@ export default {
176
176
  default: false
177
177
  },
178
178
  listHeight: {
179
- type: Number,
179
+ type: [Number, String],
180
180
  default: 256
181
181
  },
182
182
  mode: {
@@ -282,7 +282,8 @@ export default {
282
282
  return !!((this.$scopedSlots && this.$scopedSlots['icon']) || this.$slots['icon'])
283
283
  },
284
284
  listStyle() {
285
- return { maxHeight: this.listHeight + 'px' }
285
+ if (typeof this.listHeight === 'number') return { maxHeight: this.listHeight + 'px' }
286
+ return { maxHeight: this.listHeight }
286
287
  },
287
288
  externalClass() {
288
289
  // Vue 3: class is in $attrs (inheritAttrs:false keeps it off the root)
@@ -423,9 +424,16 @@ export default {
423
424
  }
424
425
  },
425
426
  registerOption(value, label) {
427
+ // Guard: SelectOption.updated() calls this on every re-render. Reassigning
428
+ // labelCache to a new object each time invalidates dependent computeds
429
+ // (selectedLabel, option.isHidden), forcing another render → updated() →
430
+ // registerOption → … an infinite loop that lags typing in searchable mode.
431
+ // Skip when the label is unchanged so the reference stays stable.
432
+ if (this.labelCache[value] === label) return
426
433
  this.labelCache = Object.assign({}, this.labelCache, { [value]: label })
427
434
  },
428
435
  setSlotOptionVisible(value, visible) {
436
+ if (this.slotOptionVisible[value] === visible) return
429
437
  this.slotOptionVisible = Object.assign({}, this.slotOptionVisible, { [value]: visible })
430
438
  },
431
439
  removeSlotOption(value) {
@@ -85,6 +85,10 @@ export default {
85
85
  if (this.select) this.select.removeSlotOption(this.value)
86
86
  },
87
87
 
88
+ // eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle, vue/no-dupe-keys
89
+ beforeDestroy() {
90
+ if (this.select) this.select.removeSlotOption(this.value)
91
+ },
88
92
  watch: {
89
93
  isHidden(val) {
90
94
  if (this.select) this.select.setSlotOptionVisible(this.value, !val)
@@ -4,7 +4,8 @@
4
4
  :class="{
5
5
  'ui-slider--vertical': orientation === 'vertical',
6
6
  'ui-slider--disabled': disabled,
7
- 'ui-slider--range': range
7
+ 'ui-slider--range': range,
8
+ [`ui-slider--${variant}`]: variant
8
9
  }"
9
10
  :style="rootStyle"
10
11
  >
@@ -88,6 +89,13 @@ export default {
88
89
  return ['horizontal', 'vertical'].indexOf(v) !== -1
89
90
  }
90
91
  },
92
+ variant: {
93
+ type: String,
94
+ default: 'neutral',
95
+ validator: function (v) {
96
+ return ['neutral', 'primary'].indexOf(v) !== -1
97
+ }
98
+ },
91
99
  disabled: { type: Boolean, default: false },
92
100
  width: { type: [String, Number], default: null },
93
101
  stepRanges: {
@@ -45,6 +45,10 @@
45
45
  pointer-events: none;
46
46
  }
47
47
 
48
+ .ui-slider--neutral .ui-slider__fill {
49
+ background: var(--primary-fg);
50
+ }
51
+
48
52
  .ui-slider--vertical .ui-slider__fill {
49
53
  top: auto;
50
54
  left: 0;
@@ -67,6 +71,10 @@
67
71
  transition: box-shadow 0.1s ease;
68
72
  }
69
73
 
74
+ .ui-slider--neutral .ui-slider__thumb {
75
+ background: var(--primary-fg);
76
+ }
77
+
70
78
  .ui-slider--vertical .ui-slider__thumb {
71
79
  top: auto;
72
80
  left: 50%;
@@ -79,6 +87,12 @@
79
87
  0 0 0 5px color-mix(in srgb, var(--primary-brand-bg) 20%, transparent);
80
88
  }
81
89
 
90
+ .ui-slider--neutral .ui-slider__thumb:hover {
91
+ box-shadow:
92
+ var(--shadow-xs),
93
+ 0 0 0 5px color-mix(in srgb, var(--primary-fg) 20%, transparent);
94
+ }
95
+
82
96
  .ui-slider__thumb:focus-visible {
83
97
  box-shadow: var(--shadow-xs), var(--shadow-focus-ring);
84
98
  }
@@ -132,7 +132,6 @@
132
132
  }
133
133
  .ui-toggle--variant-outlined.ui-toggle--active {
134
134
  background: var(--outline-active);
135
- color: var(--primary-fg);
136
135
  }
137
136
 
138
137
  /* ============================================================
@@ -165,6 +164,14 @@
165
164
  pointer-events: none;
166
165
  }
167
166
 
167
+ .ui-toggle--variant-outlined:disabled {
168
+ background: var(--secondary-bg);
169
+ }
170
+
171
+ .ui-toggle--variant-outlined.ui-toggle--active:disabled {
172
+ background: var(--tertiary-bg);
173
+ }
174
+
168
175
  /* ============================================================
169
176
  Icon-only — square button per size
170
177
  ============================================================ */
@@ -26,7 +26,12 @@
26
26
  <span class="ui-tooltip__text">
27
27
  <slot name="content">{{ title }}</slot>
28
28
  </span>
29
- <span v-if="arrow" :class="['ui-tooltip__arrow', `ui-tooltip__arrow--${side}`]" aria-hidden="true"></span>
29
+ <span
30
+ v-if="arrow"
31
+ :class="['ui-tooltip__arrow', `ui-tooltip__arrow--${side}`]"
32
+ :style="arrowStyle"
33
+ aria-hidden="true"
34
+ ></span>
30
35
  </span>
31
36
  </span>
32
37
  </template>
@@ -36,6 +41,7 @@ import { acquire, release } from '../../floating/portal-root.js'
36
41
  import { nextZIndex } from '../../floating/layer-manager.js'
37
42
 
38
43
  const GAP = 4
44
+ const EDGE_PAD = 4
39
45
 
40
46
  export default {
41
47
  name: 'Tooltip',
@@ -61,12 +67,14 @@ export default {
61
67
  data() {
62
68
  return {
63
69
  hovered: false,
64
- positionStyle: {}
70
+ triggerInView: true,
71
+ positionStyle: {},
72
+ arrowStyle: {}
65
73
  }
66
74
  },
67
75
  computed: {
68
76
  isVisible() {
69
- return this.open || this.hovered
77
+ return (this.open || this.hovered) && this.triggerInView
70
78
  },
71
79
  hasMaxWidth() {
72
80
  return this.maxWidth !== '' && this.maxWidth !== null && this.maxWidth !== undefined
@@ -81,22 +89,42 @@ export default {
81
89
  },
82
90
  watch: {
83
91
  isVisible(v) {
84
- if (v) this.$nextTick(this.updatePosition)
92
+ if (v) {
93
+ this.$nextTick(() => {
94
+ this._mountToPortal()
95
+ this.updatePosition()
96
+ })
97
+ } else {
98
+ this._unmountFromPortal()
99
+ }
85
100
  },
86
101
  side() {
87
102
  if (this.isVisible) this.$nextTick(this.updatePosition)
88
103
  }
89
104
  },
90
105
  mounted() {
91
- if (typeof document !== 'undefined' && this.$refs.tooltip) {
92
- this._tooltipPortalRoot = acquire()
93
- if (this._tooltipPortalRoot) {
94
- this._tooltipPortalRoot.appendChild(this.$refs.tooltip)
95
- }
106
+ const tip = this.$refs.tooltip
107
+ if (tip) {
108
+ this._originalParent = tip.parentNode
109
+ this._originalNextSibling = tip.nextSibling
110
+ }
111
+ if (typeof IntersectionObserver !== 'undefined' && this.$refs.trigger) {
112
+ this._triggerObserver = new IntersectionObserver(
113
+ ([entry]) => {
114
+ this.triggerInView = entry.isIntersecting
115
+ },
116
+ { threshold: 0 }
117
+ )
118
+ this._triggerObserver.observe(this.$refs.trigger)
96
119
  }
97
120
  window.addEventListener('scroll', this.onScrollOrResize, true)
98
121
  window.addEventListener('resize', this.onScrollOrResize)
99
- if (this.open) this.$nextTick(this.updatePosition)
122
+ if (this.open) {
123
+ this.$nextTick(() => {
124
+ this._mountToPortal()
125
+ this.updatePosition()
126
+ })
127
+ }
100
128
  if (typeof this.$on === 'function') {
101
129
  // eslint-disable-next-line vue/no-deprecated-events-api
102
130
  this.$on('hook:beforeDestroy', this.cleanup)
@@ -141,6 +169,17 @@ export default {
141
169
  top = t.top + (t.height - th) / 2
142
170
  left = t.right + GAP
143
171
  }
172
+ const vw = window.innerWidth
173
+ const vh = window.innerHeight
174
+ left = Math.max(EDGE_PAD, Math.min(left, vw - tw - EDGE_PAD))
175
+ top = Math.max(EDGE_PAD, Math.min(top, vh - th - EDGE_PAD))
176
+
177
+ if (this.side === 'top' || this.side === 'bottom') {
178
+ this.arrowStyle = { left: t.left + t.width / 2 - left + 'px' }
179
+ } else {
180
+ this.arrowStyle = { top: t.top + t.height / 2 - top + 'px' }
181
+ }
182
+
144
183
  this.positionStyle = {
145
184
  position: 'fixed',
146
185
  top: top + 'px',
@@ -148,11 +187,35 @@ export default {
148
187
  zIndex: nextZIndex()
149
188
  }
150
189
  },
190
+ _mountToPortal() {
191
+ const tip = this.$refs.tooltip
192
+ if (typeof document === 'undefined' || !tip) return
193
+ if (!this._tooltipPortalRoot) {
194
+ this._tooltipPortalRoot = acquire()
195
+ }
196
+ if (this._tooltipPortalRoot && tip.parentNode !== this._tooltipPortalRoot) {
197
+ this._tooltipPortalRoot.appendChild(tip)
198
+ }
199
+ },
200
+ _unmountFromPortal() {
201
+ const tip = this.$refs.tooltip
202
+ if (!tip || !this._tooltipPortalRoot) return
203
+ if (tip.parentNode === this._tooltipPortalRoot && this._originalParent) {
204
+ this._originalParent.insertBefore(tip, this._originalNextSibling || null)
205
+ }
206
+ release()
207
+ this._tooltipPortalRoot = null
208
+ },
151
209
  cleanup() {
210
+ if (this._triggerObserver) {
211
+ this._triggerObserver.disconnect()
212
+ this._triggerObserver = null
213
+ }
152
214
  window.removeEventListener('scroll', this.onScrollOrResize, true)
153
215
  window.removeEventListener('resize', this.onScrollOrResize)
154
- if (this.$refs.tooltip && this.$refs.tooltip.parentNode) {
155
- this.$refs.tooltip.parentNode.removeChild(this.$refs.tooltip)
216
+ const tip = this.$refs.tooltip
217
+ if (tip && tip.parentNode && tip.parentNode !== this._originalParent) {
218
+ tip.parentNode.removeChild(tip)
156
219
  }
157
220
  if (this._tooltipPortalRoot) {
158
221
  release()
@@ -1,34 +0,0 @@
1
- // Configured Day.js singleton for the date-picker.
2
- //
3
- // Day.js ships as a tiny core; every feature the picker needs (custom format
4
- // parsing, locale-aware week start, week/quarter numbers, inclusive comparisons)
5
- // lives in an opt-in plugin. We register them ONCE here and re-export the same
6
- // instance so every date-picker file shares one configured dayjs — importing
7
- // `dayjs` directly elsewhere would get the unconfigured core.
8
- //
9
- // `dayjs.extend` is idempotent (re-extending with the same plugin is a no-op),
10
- // so this module is safe to import from multiple files under both the Vite
11
- // (Vue 3) and webpack (Vue 2) bundlers.
12
- import dayjs from 'dayjs'
13
-
14
- import customParseFormat from 'dayjs/plugin/customParseFormat.js'
15
- import localeData from 'dayjs/plugin/localeData.js'
16
- import weekday from 'dayjs/plugin/weekday.js'
17
- import weekOfYear from 'dayjs/plugin/weekOfYear.js'
18
- import isoWeek from 'dayjs/plugin/isoWeek.js'
19
- import advancedFormat from 'dayjs/plugin/advancedFormat.js'
20
- import isSameOrBefore from 'dayjs/plugin/isSameOrBefore.js'
21
- import isSameOrAfter from 'dayjs/plugin/isSameOrAfter.js'
22
- import quarterOfYear from 'dayjs/plugin/quarterOfYear.js'
23
-
24
- dayjs.extend(customParseFormat)
25
- dayjs.extend(localeData)
26
- dayjs.extend(weekday)
27
- dayjs.extend(weekOfYear)
28
- dayjs.extend(isoWeek)
29
- dayjs.extend(advancedFormat)
30
- dayjs.extend(isSameOrBefore)
31
- dayjs.extend(isSameOrAfter)
32
- dayjs.extend(quarterOfYear)
33
-
34
- export default dayjs