webcake-ui-kit 1.1.11 → 1.1.13

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.11",
3
+ "version": "1.1.13",
4
4
  "description": "UI Kit for Vue 2 && 3 - Pure Options API",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -33,6 +33,7 @@
33
33
  "./WkEmptyIcon": "./src/components/empty-icon/EmptyIcon.vue",
34
34
  "./WkField": "./src/components/field/Field.vue",
35
35
  "./WkInput": "./src/components/input/Input.vue",
36
+ "./WkInputNumber": "./src/components/input-number/InputNumber.vue",
36
37
  "./WkMenu": "./src/components/menu/Menu.vue",
37
38
  "./WkPagination": "./src/components/pagination/Pagination.vue",
38
39
  "./WkRadio": "./src/components/radio/Radio.vue",
@@ -0,0 +1,201 @@
1
+ <template>
2
+ <span
3
+ :class="[
4
+ 'ui-input-number',
5
+ `ui-input-number--size-${size}`,
6
+ `ui-input-number--round-${roundness}`,
7
+ error && 'ui-input-number--error',
8
+ disabled && 'ui-input-number--disabled',
9
+ $attrs.class
10
+ ]"
11
+ >
12
+ <span v-if="hasPrefix" class="ui-input-number__decoration ui-input-number__prefix" aria-hidden="true">
13
+ <slot name="prefix"></slot>
14
+ </span>
15
+ <input
16
+ ref="input"
17
+ class="ui-input-number__field"
18
+ type="text"
19
+ inputmode="decimal"
20
+ :value="displayValue"
21
+ :placeholder="placeholder"
22
+ :disabled="disabled"
23
+ :readonly="readonly"
24
+ :name="name"
25
+ v-bind="{ ...$attrs, class: undefined }"
26
+ @input="onInput"
27
+ @focus="onFocus"
28
+ @blur="onBlur"
29
+ @keydown="onKeydown"
30
+ />
31
+ <span v-if="hasSuffix" class="ui-input-number__decoration ui-input-number__suffix" aria-hidden="true">
32
+ <slot name="suffix"></slot>
33
+ </span>
34
+ </span>
35
+ </template>
36
+
37
+ <script>
38
+ export default {
39
+ name: 'InputNumber',
40
+ inheritAttrs: false,
41
+ props: {
42
+ value: { default: null },
43
+ modelValue: { default: undefined },
44
+ min: { type: Number, default: null },
45
+ max: { type: Number, default: null },
46
+ step: { type: Number, default: 1 },
47
+ precision: { type: Number, default: null },
48
+ placeholder: { type: String, default: '' },
49
+ name: { type: String, default: '' },
50
+ disabled: { type: Boolean, default: false },
51
+ readonly: { type: Boolean, default: false },
52
+ size: {
53
+ type: String,
54
+ default: 'md',
55
+ validator: v => ['tiny', 'xs', 'sm', 'md', 'lg'].includes(v)
56
+ },
57
+ roundness: {
58
+ type: String,
59
+ default: 'default',
60
+ validator: v => ['default', 'round'].includes(v)
61
+ },
62
+ error: { type: Boolean, default: false }
63
+ },
64
+ emits: ['input', 'update:modelValue', 'change', 'focus', 'blur', 'pressEnter'],
65
+ data() {
66
+ return {
67
+ focused: false,
68
+ inputText: ''
69
+ }
70
+ },
71
+ computed: {
72
+ currentValue() {
73
+ return this.modelValue !== undefined ? this.modelValue : this.value
74
+ },
75
+ hasPrefix() {
76
+ return !!((this.$scopedSlots && this.$scopedSlots['prefix']) || this.$slots['prefix'])
77
+ },
78
+ hasSuffix() {
79
+ return !!((this.$scopedSlots && this.$scopedSlots['suffix']) || this.$slots['suffix'])
80
+ },
81
+ effectivePrecision() {
82
+ if (this.precision !== null) return this.precision
83
+ const s = String(this.step)
84
+ const dot = s.indexOf('.')
85
+ return dot >= 0 ? s.length - dot - 1 : 0
86
+ },
87
+ displayValue() {
88
+ if (this.focused) return this.inputText
89
+ const v = this.currentValue
90
+ if (v === null || v === undefined) return ''
91
+ const n = Number(v)
92
+ if (isNaN(n)) return ''
93
+ return n.toFixed(this.effectivePrecision)
94
+ }
95
+ },
96
+ methods: {
97
+ focus() {
98
+ if (this.$refs.input) this.$refs.input.focus()
99
+ },
100
+ blur() {
101
+ if (this.$refs.input) this.$refs.input.blur()
102
+ },
103
+ toPrecision(num) {
104
+ return parseFloat(num.toFixed(this.effectivePrecision))
105
+ },
106
+ clamp(num) {
107
+ let v = num
108
+ if (this.max !== null && v > this.max) v = this.max
109
+ if (this.min !== null && v < this.min) v = this.min
110
+ return v
111
+ },
112
+ sanitize(raw) {
113
+ let s = raw.replace(/[^\d.-]/g, '')
114
+ const hasMinus = s.charAt(0) === '-'
115
+ s = s.replace(/-/g, '')
116
+ if (hasMinus) s = '-' + s
117
+ if (this.effectivePrecision === 0) {
118
+ s = s.replace(/\./g, '')
119
+ } else {
120
+ const firstDot = s.indexOf('.')
121
+ if (firstDot >= 0) {
122
+ s = s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, '')
123
+ }
124
+ }
125
+ return s
126
+ },
127
+ commitInput() {
128
+ const raw = this.inputText
129
+ if (raw === '' || raw === '-') {
130
+ this.$emit('input', null)
131
+ this.$emit('update:modelValue', null)
132
+ this.$emit('change', null)
133
+ return
134
+ }
135
+ const num = parseFloat(raw)
136
+ if (!isNaN(num)) {
137
+ const committed = this.clamp(this.toPrecision(num))
138
+ this.$emit('input', committed)
139
+ this.$emit('update:modelValue', committed)
140
+ this.$emit('change', committed)
141
+ this.inputText = committed.toFixed(this.effectivePrecision)
142
+ }
143
+ },
144
+ onInput(e) {
145
+ const sanitized = this.sanitize(e.target.value)
146
+ if (sanitized !== e.target.value) {
147
+ const pos = Math.max(0, e.target.selectionStart - (e.target.value.length - sanitized.length))
148
+ e.target.value = sanitized
149
+ try {
150
+ e.target.setSelectionRange(pos, pos)
151
+ } catch (err) {
152
+ console.error(err)
153
+ }
154
+ }
155
+ this.inputText = sanitized
156
+ if (sanitized === '' || sanitized === '-') return
157
+ const num = parseFloat(sanitized)
158
+ if (!isNaN(num)) {
159
+ this.$emit('input', this.toPrecision(num))
160
+ this.$emit('update:modelValue', this.toPrecision(num))
161
+ }
162
+ },
163
+ onFocus(e) {
164
+ this.focused = true
165
+ const v = this.currentValue
166
+ this.inputText =
167
+ v !== null && v !== undefined ? parseFloat(Number(v).toFixed(this.effectivePrecision)).toString() : ''
168
+ this.$emit('focus', this.currentValue, e)
169
+ },
170
+ onBlur(e) {
171
+ this.focused = false
172
+ this.commitInput()
173
+ this.$emit('blur', this.currentValue, e)
174
+ },
175
+ onKeydown(e) {
176
+ if (e.key === 'ArrowUp') {
177
+ e.preventDefault()
178
+ const base = this.currentValue !== null && this.currentValue !== undefined ? Number(this.currentValue) : 0
179
+ const next = this.clamp(this.toPrecision(base + this.step))
180
+ this.$emit('input', next)
181
+ this.$emit('update:modelValue', next)
182
+ this.$emit('change', next)
183
+ this.inputText = String(next)
184
+ } else if (e.key === 'ArrowDown') {
185
+ e.preventDefault()
186
+ const base = this.currentValue !== null && this.currentValue !== undefined ? Number(this.currentValue) : 0
187
+ const next = this.clamp(this.toPrecision(base - this.step))
188
+ this.$emit('input', next)
189
+ this.$emit('update:modelValue', next)
190
+ this.$emit('change', next)
191
+ this.inputText = String(next)
192
+ } else if (e.key === 'Enter') {
193
+ this.commitInput()
194
+ this.$emit('pressEnter', this.currentValue, e)
195
+ }
196
+ }
197
+ }
198
+ }
199
+ </script>
200
+
201
+ <style src="./input-number.css" scoped></style>
@@ -0,0 +1,146 @@
1
+ .ui-input-number {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ background: var(--input);
5
+ border: 1px solid var(--border-primary);
6
+ box-shadow: var(--shadow-xs);
7
+ font-family: var(--font-family-body);
8
+ color: var(--primary-fg);
9
+ overflow: hidden;
10
+ transition:
11
+ border-color 0.12s ease,
12
+ box-shadow 0.12s ease;
13
+ }
14
+
15
+ .ui-input-number--size-md {
16
+ height: 36px;
17
+ gap: var(--spacing-xs);
18
+ padding: var(--spacing-7p5) var(--spacing-sm);
19
+ font-size: var(--paragraph-small-font-size);
20
+ line-height: var(--paragraph-small-line-height);
21
+ letter-spacing: var(--paragraph-small-letter-spacing);
22
+ }
23
+
24
+ .ui-input-number--size-lg {
25
+ height: 40px;
26
+ gap: var(--spacing-sm);
27
+ padding: var(--spacing-9p5) var(--spacing-md);
28
+ font-size: var(--paragraph-small-font-size);
29
+ line-height: var(--paragraph-small-line-height);
30
+ letter-spacing: var(--paragraph-small-letter-spacing);
31
+ }
32
+
33
+ .ui-input-number--size-sm {
34
+ height: 32px;
35
+ gap: var(--spacing-6);
36
+ padding: var(--spacing-5p5) var(--spacing-xs);
37
+ font-size: var(--paragraph-small-font-size);
38
+ line-height: var(--paragraph-small-line-height);
39
+ letter-spacing: var(--paragraph-small-letter-spacing);
40
+ }
41
+
42
+ .ui-input-number--size-xs {
43
+ height: 28px;
44
+ gap: var(--spacing-2xs);
45
+ padding: var(--spacing-6);
46
+ font-size: var(--paragraph-mini-font-size);
47
+ line-height: var(--paragraph-mini-line-height);
48
+ letter-spacing: var(--paragraph-mini-letter-spacing);
49
+ }
50
+
51
+ .ui-input-number--size-tiny {
52
+ height: 24px;
53
+ gap: var(--spacing-2xs);
54
+ padding: var(--spacing-2xs) var(--spacing-6);
55
+ font-size: var(--paragraph-mini-font-size);
56
+ line-height: var(--paragraph-mini-line-height);
57
+ letter-spacing: var(--paragraph-mini-letter-spacing);
58
+ }
59
+
60
+ .ui-input-number--round-default {
61
+ border-radius: var(--rounded-xl);
62
+ }
63
+
64
+ .ui-input-number--size-xs.ui-input-number--round-default {
65
+ border-radius: var(--radius);
66
+ }
67
+
68
+ .ui-input-number--size-tiny.ui-input-number--round-default {
69
+ border-radius: var(--rounded-lg);
70
+ }
71
+
72
+ .ui-input-number--round-round {
73
+ border-radius: var(--rounded-full);
74
+ }
75
+
76
+ .ui-input-number:focus-within {
77
+ border-color: var(--border-focus);
78
+ box-shadow: 0 0 0 3px var(--focus-ring);
79
+ }
80
+
81
+ .ui-input-number--error {
82
+ border-color: var(--destructive-border);
83
+ box-shadow: none;
84
+ }
85
+
86
+ .ui-input-number--error:focus-within {
87
+ border-color: var(--destructive-border);
88
+ box-shadow: 0 0 0 3px var(--focus-ring-error);
89
+ }
90
+
91
+ .ui-input-number--disabled {
92
+ opacity: 0.5;
93
+ }
94
+
95
+ .ui-input-number--disabled:focus-within {
96
+ border-color: var(--border-primary);
97
+ box-shadow: var(--shadow-xs);
98
+ }
99
+
100
+ .ui-input-number__field {
101
+ flex: 1 1 0;
102
+ min-width: 0;
103
+ width: 100%;
104
+ margin: 0;
105
+ padding: 0;
106
+ background: transparent;
107
+ border: 0;
108
+ outline: 0;
109
+ color: inherit;
110
+ font: inherit;
111
+ letter-spacing: inherit;
112
+ -moz-appearance: textfield;
113
+ appearance: textfield;
114
+ }
115
+
116
+ .ui-input-number__field::-webkit-inner-spin-button,
117
+ .ui-input-number__field::-webkit-outer-spin-button {
118
+ -webkit-appearance: none;
119
+ appearance: none;
120
+ margin: 0;
121
+ }
122
+
123
+ .ui-input-number__field::placeholder {
124
+ color: var(--muted-fg);
125
+ opacity: 1;
126
+ }
127
+
128
+ .ui-input-number__field:disabled {
129
+ cursor: not-allowed;
130
+ }
131
+
132
+ .ui-input-number__decoration {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ flex-shrink: 0;
137
+ width: 20px;
138
+ height: 20px;
139
+ color: var(--muted-fg);
140
+ }
141
+
142
+ .ui-input-number--size-xs .ui-input-number__decoration,
143
+ .ui-input-number--size-tiny .ui-input-number__decoration {
144
+ width: 16px;
145
+ height: 16px;
146
+ }
@@ -39,7 +39,7 @@ export default {
39
39
  },
40
40
 
41
41
  props: {
42
- value: { type: [String, Number], default: null },
42
+ value: { default: null },
43
43
  modelValue: { default: null },
44
44
  tabs: {
45
45
  type: Array,
package/src/index.js CHANGED
@@ -16,6 +16,7 @@ export { default as WkEmpty } from './components/empty/Empty.vue'
16
16
  export { default as WkEmptyIcon } from './components/empty-icon/EmptyIcon.vue'
17
17
  export { default as WkField } from './components/field/Field.vue'
18
18
  export { default as WkInput } from './components/input/Input.vue'
19
+ export { default as WkInputNumber } from './components/input-number/InputNumber.vue'
19
20
  export { default as WkMenu } from './components/menu/Menu.vue'
20
21
  export { default as WkPagination } from './components/pagination/Pagination.vue'
21
22
  export { default as WkRadio } from './components/radio/Radio.vue'