webcake-ui-kit 1.1.38 → 1.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.
Files changed (68) hide show
  1. package/package.json +8 -2
  2. package/src/components/checkbox/Checkbox.vue +1 -1
  3. package/src/components/drawer/Drawer.vue +261 -0
  4. package/src/components/drawer/drawer.css +162 -0
  5. package/src/components/dropdown/Dropdown.vue +15 -9
  6. package/src/components/hover-card/HoverCard.vue +159 -0
  7. package/src/components/hover-card/hover-card.css +10 -0
  8. package/src/components/input-counter/InputCounter.vue +250 -0
  9. package/src/components/input-counter/input-counter.css +209 -0
  10. package/src/components/menu/Menu.vue +4 -2
  11. package/src/components/pagination/Pagination.vue +12 -3
  12. package/src/components/popover/Popover.vue +144 -0
  13. package/src/components/popover/popover.css +3 -0
  14. package/src/components/table-v2/ColumnDragHandle.vue +103 -0
  15. package/src/components/table-v2/Table.vue +299 -0
  16. package/src/components/table-v2/TableColGroup.vue +55 -0
  17. package/src/components/table-v2/TableEmpty.vue +10 -0
  18. package/src/components/table-v2/TableLoading.vue +25 -0
  19. package/src/components/table-v2/TableResizeObserver.vue +93 -0
  20. package/src/components/table-v2/TableWrapper.vue +193 -0
  21. package/src/components/table-v2/body/DragPreview.vue +65 -0
  22. package/src/components/table-v2/body/MeasureCell.vue +42 -0
  23. package/src/components/table-v2/body/TableBody.vue +126 -0
  24. package/src/components/table-v2/body/TableCell.vue +108 -0
  25. package/src/components/table-v2/body/TableRow.vue +73 -0
  26. package/src/components/table-v2/composables/useColumns.js +19 -0
  27. package/src/components/table-v2/composables/useSorter.js +12 -0
  28. package/src/components/table-v2/composables/useSticky.js +31 -0
  29. package/src/components/table-v2/constants.js +2 -0
  30. package/src/components/table-v2/context/createTableContext.js +79 -0
  31. package/src/components/table-v2/header/TableHeader.vue +15 -0
  32. package/src/components/table-v2/header/TableHeaderCell.vue +89 -0
  33. package/src/components/table-v2/header/TableHeaderRow.vue +107 -0
  34. package/src/components/table-v2/style/table-body.css +0 -0
  35. package/src/components/table-v2/style/table-header.css +0 -0
  36. package/src/components/table-v2/style/table-loading.css +19 -0
  37. package/src/components/table-v2/style/table-wrapper.css +287 -0
  38. package/src/components/table-v2/style/table.css +5 -0
  39. package/src/icons/BoxShadow.vue +35 -0
  40. package/src/icons/Carousel.vue +34 -0
  41. package/src/icons/CloudSync2.vue +42 -0
  42. package/src/icons/CornerBottomLeft.vue +26 -0
  43. package/src/icons/CornerBottomRight.vue +26 -0
  44. package/src/icons/CornerTopLeft.vue +26 -0
  45. package/src/icons/CornerTopRight.vue +26 -0
  46. package/src/icons/DistanceRight.vue +30 -0
  47. package/src/icons/DistanceTop.vue +30 -0
  48. package/src/icons/DropShadow.vue +35 -0
  49. package/src/icons/Fire.vue +26 -0
  50. package/src/icons/GlobeAccess.vue +32 -0
  51. package/src/icons/InnerShadow.vue +33 -0
  52. package/src/icons/Livestream.vue +54 -0
  53. package/src/icons/PaddingBottom.vue +30 -0
  54. package/src/icons/PaddingLeft.vue +30 -0
  55. package/src/icons/PaddingRight.vue +30 -0
  56. package/src/icons/PaddingTop.vue +30 -0
  57. package/src/icons/SkewHeight.vue +32 -0
  58. package/src/icons/SkewWidth.vue +32 -0
  59. package/src/icons/SquareDashedBottomSolid.vue +26 -0
  60. package/src/icons/SquareDashedLeftSolid.vue +26 -0
  61. package/src/icons/SquareDashedRightSolid.vue +26 -0
  62. package/src/icons/StrokeLine.vue +23 -0
  63. package/src/icons/Topline.vue +26 -0
  64. package/src/icons/index.js +25 -0
  65. package/src/index.js +5 -0
  66. package/src/styles/.omc/state/agent-replay-645326b7-372b-463d-ab45-0adaafe31a51.jsonl +2 -0
  67. package/src/styles/.omc/state/subagent-tracking.json +7 -0
  68. package/src/utils/common.js +34 -0
@@ -0,0 +1,250 @@
1
+ <template>
2
+ <span
3
+ :class="[
4
+ 'ui-input-counter',
5
+ `ui-input-counter--size-${size}`,
6
+ `ui-input-counter--round-${roundness}`,
7
+ error && 'ui-input-counter--error',
8
+ disabled && 'ui-input-counter--disabled',
9
+ $attrs.class
10
+ ]"
11
+ >
12
+ <span class="ui-input-counter__main">
13
+ <span v-if="hasPrefix" class="ui-input-counter__decoration ui-input-counter__prefix" aria-hidden="true">
14
+ <slot name="prefix"></slot>
15
+ </span>
16
+ <input
17
+ ref="input"
18
+ class="ui-input-counter__field"
19
+ type="text"
20
+ inputmode="decimal"
21
+ :style="centered ? { textAlign: 'center' } : {}"
22
+ :value="displayValue"
23
+ :placeholder="placeholder"
24
+ :disabled="disabled"
25
+ :readonly="readonly"
26
+ :name="name"
27
+ v-bind="{ ...$attrs, class: undefined }"
28
+ @input="onInput"
29
+ @focus="onFocus"
30
+ @blur="onBlur"
31
+ @keydown="onKeydown"
32
+ />
33
+ <span v-if="hasSuffix" class="ui-input-counter__decoration ui-input-counter__suffix" aria-hidden="true">
34
+ <slot name="suffix"></slot>
35
+ </span>
36
+ </span>
37
+ <span class="ui-input-counter__controls">
38
+ <button
39
+ type="button"
40
+ class="ui-input-counter__btn ui-input-counter__btn--minus"
41
+ aria-label="Decrease"
42
+ tabindex="-1"
43
+ :disabled="!canDecrement"
44
+ @mousedown.prevent
45
+ @click="decrement"
46
+ >
47
+ <WkiMinus :size="12" />
48
+ </button>
49
+ <button
50
+ type="button"
51
+ class="ui-input-counter__btn ui-input-counter__btn--add"
52
+ aria-label="Increase"
53
+ tabindex="-1"
54
+ :disabled="!canIncrement"
55
+ @mousedown.prevent
56
+ @click="increment"
57
+ >
58
+ <WkiPlus :size="12" />
59
+ </button>
60
+ </span>
61
+ </span>
62
+ </template>
63
+
64
+ <script>
65
+ import { WkiMinus, WkiPlus } from '../../icons'
66
+
67
+ export default {
68
+ name: 'InputCounter',
69
+ inheritAttrs: false,
70
+ components: {
71
+ WkiMinus,
72
+ WkiPlus
73
+ },
74
+ props: {
75
+ value: { default: null },
76
+ modelValue: { default: undefined },
77
+ min: { type: Number, default: null },
78
+ max: { type: Number, default: null },
79
+ step: { type: Number, default: 1 },
80
+ precision: { type: Number, default: null },
81
+ placeholder: { type: String, default: '' },
82
+ name: { type: String, default: '' },
83
+ disabled: { type: Boolean, default: false },
84
+ readonly: { type: Boolean, default: false },
85
+ error: { type: Boolean, default: false },
86
+ centered: { type: Boolean, default: false },
87
+ size: {
88
+ type: String,
89
+ default: 'md',
90
+ validator: v => ['xs', 'sm', 'md', 'lg'].includes(v)
91
+ },
92
+ roundness: {
93
+ type: String,
94
+ default: 'default',
95
+ validator: v => ['default', 'round'].includes(v)
96
+ }
97
+ },
98
+ emits: ['input', 'update:modelValue', 'change', 'focus', 'blur', 'pressEnter'],
99
+ data() {
100
+ return {
101
+ focused: false,
102
+ inputText: ''
103
+ }
104
+ },
105
+ computed: {
106
+ currentValue() {
107
+ return this.modelValue !== undefined ? this.modelValue : this.value
108
+ },
109
+ hasPrefix() {
110
+ return !!((this.$scopedSlots && this.$scopedSlots['prefix']) || this.$slots['prefix'])
111
+ },
112
+ hasSuffix() {
113
+ return !!((this.$scopedSlots && this.$scopedSlots['suffix']) || this.$slots['suffix'])
114
+ },
115
+ effectivePrecision() {
116
+ if (this.precision !== null) return this.precision
117
+ const s = String(this.step)
118
+ const dot = s.indexOf('.')
119
+ return dot >= 0 ? s.length - dot - 1 : 0
120
+ },
121
+ numericValue() {
122
+ const v = this.currentValue
123
+ if (v === null || v === undefined || v === '') return null
124
+ const n = Number(v)
125
+ return isNaN(n) ? null : n
126
+ },
127
+ canDecrement() {
128
+ if (this.disabled || this.readonly) return false
129
+ if (this.min === null) return true
130
+ return (this.numericValue === null ? 0 : this.numericValue) > this.min
131
+ },
132
+ canIncrement() {
133
+ if (this.disabled || this.readonly) return false
134
+ if (this.max === null) return true
135
+ return (this.numericValue === null ? 0 : this.numericValue) < this.max
136
+ },
137
+ displayValue() {
138
+ if (this.focused) return this.inputText
139
+ if (this.numericValue === null) return ''
140
+ return this.numericValue.toFixed(this.effectivePrecision)
141
+ }
142
+ },
143
+ methods: {
144
+ focus() {
145
+ if (this.$refs.input) this.$refs.input.focus()
146
+ },
147
+ blur() {
148
+ if (this.$refs.input) this.$refs.input.blur()
149
+ },
150
+ toPrecision(num) {
151
+ return parseFloat(num.toFixed(this.effectivePrecision))
152
+ },
153
+ clamp(num) {
154
+ let v = num
155
+ if (this.max !== null && v > this.max) v = this.max
156
+ if (this.min !== null && v < this.min) v = this.min
157
+ return v
158
+ },
159
+ sanitize(raw) {
160
+ let s = raw.replace(/[^\d.-]/g, '')
161
+ const hasMinus = s.charAt(0) === '-'
162
+ s = s.replace(/-/g, '')
163
+ if (hasMinus) s = '-' + s
164
+ if (this.effectivePrecision === 0) {
165
+ s = s.replace(/\./g, '')
166
+ } else {
167
+ const firstDot = s.indexOf('.')
168
+ if (firstDot >= 0) {
169
+ s = s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, '')
170
+ }
171
+ }
172
+ return s
173
+ },
174
+ emitValue(v) {
175
+ this.$emit('input', v)
176
+ this.$emit('update:modelValue', v)
177
+ this.$emit('change', v)
178
+ },
179
+ stepBy(direction) {
180
+ if (this.disabled || this.readonly) return
181
+ const base = this.numericValue === null ? 0 : this.numericValue
182
+ const next = this.clamp(this.toPrecision(base + direction * this.step))
183
+ this.emitValue(next)
184
+ if (this.focused) this.inputText = String(next)
185
+ },
186
+ increment() {
187
+ this.stepBy(1)
188
+ },
189
+ decrement() {
190
+ this.stepBy(-1)
191
+ },
192
+ commitInput() {
193
+ const raw = this.inputText
194
+ if (raw === '' || raw === '-') {
195
+ this.emitValue(null)
196
+ return
197
+ }
198
+ const num = parseFloat(raw)
199
+ if (!isNaN(num)) {
200
+ const committed = this.clamp(this.toPrecision(num))
201
+ this.emitValue(committed)
202
+ this.inputText = committed.toFixed(this.effectivePrecision)
203
+ }
204
+ },
205
+ onInput(e) {
206
+ const sanitized = this.sanitize(e.target.value)
207
+ if (sanitized !== e.target.value) {
208
+ const pos = Math.max(0, e.target.selectionStart - (e.target.value.length - sanitized.length))
209
+ e.target.value = sanitized
210
+ try {
211
+ e.target.setSelectionRange(pos, pos)
212
+ } catch (err) {
213
+ console.error(err)
214
+ }
215
+ }
216
+ this.inputText = sanitized
217
+ if (sanitized === '' || sanitized === '-') return
218
+ const num = parseFloat(sanitized)
219
+ if (!isNaN(num)) {
220
+ this.$emit('input', this.toPrecision(num))
221
+ this.$emit('update:modelValue', this.toPrecision(num))
222
+ }
223
+ },
224
+ onFocus(e) {
225
+ this.focused = true
226
+ this.inputText = this.numericValue === null ? '' : String(this.numericValue)
227
+ this.$emit('focus', this.currentValue, e)
228
+ },
229
+ onBlur(e) {
230
+ this.focused = false
231
+ this.commitInput()
232
+ this.$emit('blur', this.currentValue, e)
233
+ },
234
+ onKeydown(e) {
235
+ if (e.key === 'ArrowUp') {
236
+ e.preventDefault()
237
+ this.increment()
238
+ } else if (e.key === 'ArrowDown') {
239
+ e.preventDefault()
240
+ this.decrement()
241
+ } else if (e.key === 'Enter') {
242
+ this.commitInput()
243
+ this.$emit('pressEnter', this.currentValue, e)
244
+ }
245
+ }
246
+ }
247
+ }
248
+ </script>
249
+
250
+ <style src="./input-counter.css" scoped></style>
@@ -0,0 +1,209 @@
1
+ /* Shell — mirrors the vetted Input/InputNumber shell so the counter sits
2
+ consistently alongside the other form controls. */
3
+ .ui-input-counter {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ background: var(--input);
7
+ border: 1px solid var(--border-primary);
8
+ box-shadow: var(--shadow-xs);
9
+ font-family: var(--font-family-body);
10
+ color: var(--primary-fg);
11
+ transition:
12
+ border-color 0.12s ease,
13
+ box-shadow 0.12s ease;
14
+ }
15
+
16
+ /* Size — heights, paddings, gaps, typography. Right padding is trimmed vs the
17
+ plain input because the stepper buttons occupy the trailing edge. */
18
+ .ui-input-counter--size-md {
19
+ height: 36px;
20
+ gap: var(--spacing-sm);
21
+ padding: var(--spacing-7p5) var(--spacing-xs) var(--spacing-7p5) var(--spacing-sm);
22
+ font-size: var(--paragraph-small-font-size);
23
+ line-height: var(--paragraph-small-line-height);
24
+ letter-spacing: var(--paragraph-small-letter-spacing);
25
+ }
26
+
27
+ .ui-input-counter--size-lg {
28
+ height: 40px;
29
+ gap: var(--spacing-sm);
30
+ padding: var(--spacing-9p5) var(--spacing-6) var(--spacing-9p5) var(--spacing-md);
31
+ font-size: var(--paragraph-small-font-size);
32
+ line-height: var(--paragraph-small-line-height);
33
+ letter-spacing: var(--paragraph-small-letter-spacing);
34
+ }
35
+
36
+ .ui-input-counter--size-sm {
37
+ height: 32px;
38
+ gap: var(--spacing-6);
39
+ padding: var(--spacing-5p5) var(--spacing-6) var(--spacing-5p5) var(--spacing-xs);
40
+ font-size: var(--paragraph-small-font-size);
41
+ line-height: var(--paragraph-small-line-height);
42
+ letter-spacing: var(--paragraph-small-letter-spacing);
43
+ }
44
+
45
+ .ui-input-counter--size-xs {
46
+ height: 28px;
47
+ gap: var(--spacing-6);
48
+ padding: var(--spacing-2xs) var(--spacing-2xs) var(--spacing-2xs) var(--spacing-6);
49
+ font-size: var(--paragraph-mini-font-size);
50
+ line-height: var(--paragraph-mini-line-height);
51
+ letter-spacing: var(--paragraph-mini-letter-spacing);
52
+ }
53
+
54
+ .ui-input-counter--size-tiny {
55
+ height: 24px;
56
+ gap: var(--spacing-2xs);
57
+ padding: var(--spacing-2xs) var(--spacing-2xs) var(--spacing-2xs) var(--spacing-6);
58
+ font-size: var(--paragraph-mini-font-size);
59
+ line-height: var(--paragraph-mini-line-height);
60
+ letter-spacing: var(--paragraph-mini-letter-spacing);
61
+ }
62
+
63
+ /* Roundness — Mini uses the smaller default radius per Figma (rounded-xl → radius). */
64
+ .ui-input-counter--round-default {
65
+ border-radius: var(--rounded-xl);
66
+ }
67
+
68
+ .ui-input-counter--size-xs.ui-input-counter--round-default {
69
+ border-radius: var(--radius);
70
+ }
71
+
72
+ .ui-input-counter--size-tiny.ui-input-counter--round-default {
73
+ border-radius: var(--rounded-lg);
74
+ }
75
+
76
+ .ui-input-counter--round-round {
77
+ border-radius: var(--rounded-full);
78
+ }
79
+
80
+ /* Focus (interactive) — wrapper highlights when the inner field is focused. */
81
+ .ui-input-counter:focus-within {
82
+ border-color: var(--border-focus);
83
+ box-shadow: 0 0 0 3px var(--focus-ring);
84
+ }
85
+
86
+ /* Error (logical) — red border, no base shadow. */
87
+ .ui-input-counter--error {
88
+ border-color: var(--destructive-border);
89
+ box-shadow: none;
90
+ }
91
+
92
+ .ui-input-counter--error:focus-within {
93
+ border-color: var(--destructive-border);
94
+ box-shadow: 0 0 0 3px var(--focus-ring-error);
95
+ }
96
+
97
+ /* Disabled — visual only; buttons + field carry their own disabled state. */
98
+ .ui-input-counter--disabled {
99
+ opacity: 0.5;
100
+ background: var(--secondary-bg);
101
+ }
102
+
103
+ .ui-input-counter--disabled:focus-within {
104
+ border-color: var(--border-primary);
105
+ box-shadow: var(--shadow-xs);
106
+ }
107
+
108
+ /* Left group — prefix + field + suffix, grows to fill available width. */
109
+ .ui-input-counter__main {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ flex: 1 1 0;
113
+ min-width: 0;
114
+ gap: var(--spacing-2xs);
115
+ overflow: hidden;
116
+ }
117
+
118
+ /* The native input — strips browser chrome and inherits typography from shell. */
119
+ .ui-input-counter__field {
120
+ flex: 1 1 0;
121
+ min-width: 0;
122
+ width: 100%;
123
+ margin: 0;
124
+ padding: 0;
125
+ background: transparent;
126
+ border: 0;
127
+ outline: 0;
128
+ color: inherit;
129
+ font: inherit;
130
+ letter-spacing: inherit;
131
+ -moz-appearance: textfield;
132
+ appearance: textfield;
133
+ }
134
+
135
+ .ui-input-counter__field::-webkit-inner-spin-button,
136
+ .ui-input-counter__field::-webkit-outer-spin-button {
137
+ -webkit-appearance: none;
138
+ appearance: none;
139
+ margin: 0;
140
+ }
141
+
142
+ .ui-input-counter__field::placeholder {
143
+ color: var(--muted-fg);
144
+ opacity: 1;
145
+ }
146
+
147
+ .ui-input-counter__field:disabled {
148
+ cursor: not-allowed;
149
+ }
150
+
151
+ /* Decoration slot wrappers — prepend/append hints, muted like the placeholder. */
152
+ .ui-input-counter__decoration {
153
+ display: inline-flex;
154
+ align-items: center;
155
+ justify-content: center;
156
+ flex-shrink: 0;
157
+ color: var(--muted-fg);
158
+ }
159
+
160
+ /* Stepper group — two segmented mini buttons pinned to the trailing edge. */
161
+ .ui-input-counter__controls {
162
+ display: inline-flex;
163
+ align-items: center;
164
+ flex-shrink: 0;
165
+ gap: var(--spacing-2xs);
166
+ }
167
+
168
+ .ui-input-counter__btn {
169
+ display: inline-flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ flex-shrink: 0;
173
+ width: 20px;
174
+ height: 20px;
175
+ padding: 0;
176
+ background: var(--input);
177
+ color: var(--secondary-fg);
178
+ border: 1px solid var(--border-primary);
179
+ border-radius: var(--rounded-lg);
180
+ cursor: pointer;
181
+ transition:
182
+ background-color 0.12s ease,
183
+ border-color 0.12s ease,
184
+ box-shadow 0.12s ease;
185
+ }
186
+
187
+ .ui-input-counter__btn > svg {
188
+ display: block;
189
+ width: 12px;
190
+ height: 12px;
191
+ }
192
+
193
+ .ui-input-counter__btn:hover {
194
+ background: var(--ghost-hover);
195
+ }
196
+
197
+ .ui-input-counter__btn:active {
198
+ background: var(--secondary-bg);
199
+ }
200
+
201
+ .ui-input-counter__btn:focus-visible {
202
+ outline: none;
203
+ box-shadow: 0 0 0 3px var(--focus-ring);
204
+ }
205
+
206
+ .ui-input-counter__btn:disabled {
207
+ opacity: 0.5;
208
+ cursor: not-allowed;
209
+ }
@@ -65,6 +65,8 @@ export default {
65
65
  offset: { type: Number, default: 4 },
66
66
  closeOnSelect: { type: Boolean, default: true },
67
67
  closeOnEsc: { type: Boolean, default: true },
68
+ closeOnTab: { type: Boolean, default: true },
69
+ autoFocus: { type: Boolean, default: true },
68
70
  disabled: { type: Boolean, default: false },
69
71
  persistent: { type: Boolean, default: false },
70
72
  anchorWidth: { type: Boolean, default: false },
@@ -196,7 +198,7 @@ export default {
196
198
  this._kmSetFloatEl(float)
197
199
  this._pmAttach(this._triggerEl, float)
198
200
  this._coAttach()
199
- this._fmFocus(float)
201
+ if (this.autoFocus) this._fmFocus(float)
200
202
  })
201
203
  },
202
204
  handleClose() {
@@ -212,7 +214,7 @@ export default {
212
214
  if (this.closeOnEsc) this.closeMenu()
213
215
  },
214
216
  _kmOnTab() {
215
- this.closeMenu()
217
+ if (this.closeOnTab) this.closeMenu()
216
218
  },
217
219
  _coOnOutside() {
218
220
  if (this.persistent) return
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <nav class="ui-pagination" role="navigation" :aria-label="ariaLabel" :class="{ 'ui-pagination--disabled': disabled }">
3
- <Button variant="ghost" :disabled="isPrevDisabled" @click="goPrev" :label="prevLabel || ''">
3
+ <Button variant="ghost" :size="size" :disabled="isPrevDisabled" @click="goPrev" :label="prevLabel || ''">
4
4
  <template v-if="showIcon" #icon-left>
5
5
  <WkiChevronLeft />
6
6
  </template>
@@ -11,6 +11,7 @@
11
11
  v-if="typeof item === 'number'"
12
12
  :key="'page-' + item"
13
13
  :variant="currentPage === item ? 'outline' : 'ghost'"
14
+ :size="size"
14
15
  :disabled="disabled"
15
16
  @click="select(item)"
16
17
  >
@@ -20,6 +21,7 @@
20
21
  v-else
21
22
  :key="`ellipsis-${idx}-${item}`"
22
23
  variant="ghost"
24
+ :size="size"
23
25
  :disabled="disabled"
24
26
  @click="onEllipsisClick(item)"
25
27
  >
@@ -29,7 +31,7 @@
29
31
  </Button>
30
32
  </template>
31
33
 
32
- <Button variant="ghost" :disabled="isNextDisabled" @click="goNext" :label="nextLabel || ''">
34
+ <Button variant="ghost" :size="size" :disabled="isNextDisabled" @click="goNext" :label="nextLabel || ''">
33
35
  <template v-if="showIcon" #icon-right>
34
36
  <WkiChevronRight />
35
37
  </template>
@@ -71,7 +73,14 @@ export default {
71
73
  ariaLabel: { type: String, default: 'Pagination' },
72
74
  pageAriaLabel: { type: Function, default: null },
73
75
  disabled: { type: Boolean, default: false },
74
- jumpStep: { type: Number, default: 4 }
76
+ jumpStep: { type: Number, default: 4 },
77
+ size: {
78
+ type: String,
79
+ default: 'sm',
80
+ validator: function (v) {
81
+ return ['mini', 'xs', 'sm', 'md', 'lg', 'xl'].includes(v)
82
+ }
83
+ }
75
84
  },
76
85
 
77
86
  emits: ['change', 'update:modelValue'],
@@ -0,0 +1,144 @@
1
+ <template>
2
+ <Menu
3
+ :open="isOpen"
4
+ :placement="placement"
5
+ :offset="offset"
6
+ :close-on-esc="closeOnEsc"
7
+ :close-on-tab="false"
8
+ :disabled="disabled"
9
+ :persistent="persistent"
10
+ :anchor-width="anchorWidth"
11
+ :width="width"
12
+ :overlay-class-name="overlayClassName"
13
+ :overlay-style="overlayStyle"
14
+ @change="onOpenChange"
15
+ @open="$emit('open')"
16
+ @close="$emit('close')"
17
+ >
18
+ <template #trigger="{ triggerRef }">
19
+ <span
20
+ :ref="triggerRef"
21
+ class="ui-popover__anchor"
22
+ @click="triggerHasClick && toggle()"
23
+ @mouseenter="triggerHasHover && handleMouseEnter()"
24
+ @mouseleave="triggerHasHover && handleMouseLeave()"
25
+ >
26
+ <slot name="trigger" :is-open="isOpen"></slot>
27
+ </span>
28
+ </template>
29
+ <div
30
+ style="display: contents"
31
+ @mouseenter="triggerHasHover && handleMouseEnter()"
32
+ @mouseleave="triggerHasHover && handleMouseLeave()"
33
+ >
34
+ <slot :close="close" :is-open="isOpen"></slot>
35
+ </div>
36
+ </Menu>
37
+ </template>
38
+
39
+ <script>
40
+ import Menu from '../menu/Menu.vue'
41
+
42
+ const PLACEMENTS = [
43
+ 'top',
44
+ 'top-start',
45
+ 'top-end',
46
+ 'bottom',
47
+ 'bottom-start',
48
+ 'bottom-end',
49
+ 'left',
50
+ 'left-start',
51
+ 'left-end',
52
+ 'right',
53
+ 'right-start',
54
+ 'right-end'
55
+ ]
56
+
57
+ export default {
58
+ name: 'Popover',
59
+ components: { Menu },
60
+ model: { prop: 'open', event: 'change' },
61
+ props: {
62
+ open: { type: Boolean, default: false },
63
+ modelValue: { type: Boolean, default: undefined },
64
+ placement: {
65
+ type: String,
66
+ default: 'bottom-start',
67
+ validator: v => PLACEMENTS.indexOf(v) !== -1
68
+ },
69
+ offset: { type: Number, default: 8 },
70
+ // 'click', 'hover', or both
71
+ trigger: {
72
+ type: Array,
73
+ default: function () {
74
+ return ['click']
75
+ }
76
+ },
77
+ closeOnEsc: { type: Boolean, default: true },
78
+ disabled: { type: Boolean, default: false },
79
+ persistent: { type: Boolean, default: false },
80
+ anchorWidth: { type: Boolean, default: false },
81
+ width: { type: [String, Number], default: null },
82
+ overlayClassName: { type: [String, Array, Object], default: null },
83
+ overlayStyle: { type: [Object, Array], default: null }
84
+ },
85
+ emits: ['change', 'update:modelValue', 'open', 'close'],
86
+ data() {
87
+ return {
88
+ isOpen: this.modelValue !== undefined ? this.modelValue : this.open
89
+ }
90
+ },
91
+ computed: {
92
+ triggerHasClick() {
93
+ return this.trigger.indexOf('click') !== -1
94
+ },
95
+ triggerHasHover() {
96
+ return this.trigger.indexOf('hover') !== -1
97
+ }
98
+ },
99
+ watch: {
100
+ open(v) {
101
+ if (this.modelValue === undefined) this.isOpen = v
102
+ },
103
+ modelValue(v) {
104
+ if (v !== undefined) this.isOpen = v
105
+ }
106
+ },
107
+ methods: {
108
+ onOpenChange(v) {
109
+ this.isOpen = v
110
+ this.$emit('change', v)
111
+ this.$emit('update:modelValue', v)
112
+ },
113
+ toggle() {
114
+ if (this.disabled) return
115
+ this.onOpenChange(!this.isOpen)
116
+ },
117
+ close() {
118
+ this.onOpenChange(false)
119
+ },
120
+ handleMouseEnter() {
121
+ if (this.disabled) return
122
+ if (this._hoverTimer) {
123
+ clearTimeout(this._hoverTimer)
124
+ this._hoverTimer = null
125
+ }
126
+ this.onOpenChange(true)
127
+ },
128
+ handleMouseLeave() {
129
+ this._hoverTimer = setTimeout(() => {
130
+ this.onOpenChange(false)
131
+ }, 80)
132
+ }
133
+ },
134
+ beforeUnmount() {
135
+ if (this._hoverTimer) clearTimeout(this._hoverTimer)
136
+ },
137
+ // eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle, vue/no-dupe-keys
138
+ beforeDestroy() {
139
+ if (this._hoverTimer) clearTimeout(this._hoverTimer)
140
+ }
141
+ }
142
+ </script>
143
+
144
+ <style src="./popover.css" scoped></style>
@@ -0,0 +1,3 @@
1
+ .ui-popover__anchor {
2
+ display: inline-block;
3
+ }