webcake-ui-kit 1.1.11 → 1.1.12

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.12",
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,189 @@
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
+ <input
13
+ ref="input"
14
+ class="ui-input-number__field"
15
+ type="text"
16
+ inputmode="decimal"
17
+ :value="displayValue"
18
+ :placeholder="placeholder"
19
+ :disabled="disabled"
20
+ :readonly="readonly"
21
+ :name="name"
22
+ v-bind="{ ...$attrs, class: undefined }"
23
+ @input="onInput"
24
+ @focus="onFocus"
25
+ @blur="onBlur"
26
+ @keydown="onKeydown"
27
+ />
28
+ </span>
29
+ </template>
30
+
31
+ <script>
32
+ export default {
33
+ name: 'InputNumber',
34
+ inheritAttrs: false,
35
+ props: {
36
+ value: { default: null },
37
+ modelValue: { default: undefined },
38
+ min: { type: Number, default: null },
39
+ max: { type: Number, default: null },
40
+ step: { type: Number, default: 1 },
41
+ precision: { type: Number, default: null },
42
+ placeholder: { type: String, default: '' },
43
+ name: { type: String, default: '' },
44
+ disabled: { type: Boolean, default: false },
45
+ readonly: { type: Boolean, default: false },
46
+ size: {
47
+ type: String,
48
+ default: 'md',
49
+ validator: v => ['tiny', 'xs', 'sm', 'md', 'lg'].includes(v)
50
+ },
51
+ roundness: {
52
+ type: String,
53
+ default: 'default',
54
+ validator: v => ['default', 'round'].includes(v)
55
+ },
56
+ error: { type: Boolean, default: false }
57
+ },
58
+ emits: ['input', 'update:modelValue', 'change', 'focus', 'blur', 'pressEnter'],
59
+ data() {
60
+ return {
61
+ focused: false,
62
+ inputText: ''
63
+ }
64
+ },
65
+ computed: {
66
+ currentValue() {
67
+ return this.modelValue !== undefined ? this.modelValue : this.value
68
+ },
69
+ effectivePrecision() {
70
+ if (this.precision !== null) return this.precision
71
+ const s = String(this.step)
72
+ const dot = s.indexOf('.')
73
+ return dot >= 0 ? s.length - dot - 1 : 0
74
+ },
75
+ displayValue() {
76
+ if (this.focused) return this.inputText
77
+ const v = this.currentValue
78
+ if (v === null || v === undefined) return ''
79
+ const n = Number(v)
80
+ if (isNaN(n)) return ''
81
+ return n.toFixed(this.effectivePrecision)
82
+ }
83
+ },
84
+ methods: {
85
+ focus() {
86
+ if (this.$refs.input) this.$refs.input.focus()
87
+ },
88
+ blur() {
89
+ if (this.$refs.input) this.$refs.input.blur()
90
+ },
91
+ toPrecision(num) {
92
+ return parseFloat(num.toFixed(this.effectivePrecision))
93
+ },
94
+ clamp(num) {
95
+ let v = num
96
+ if (this.max !== null && v > this.max) v = this.max
97
+ if (this.min !== null && v < this.min) v = this.min
98
+ return v
99
+ },
100
+ sanitize(raw) {
101
+ let s = raw.replace(/[^\d.-]/g, '')
102
+ const hasMinus = s.charAt(0) === '-'
103
+ s = s.replace(/-/g, '')
104
+ if (hasMinus) s = '-' + s
105
+ if (this.effectivePrecision === 0) {
106
+ s = s.replace(/\./g, '')
107
+ } else {
108
+ const firstDot = s.indexOf('.')
109
+ if (firstDot >= 0) {
110
+ s = s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, '')
111
+ }
112
+ }
113
+ return s
114
+ },
115
+ commitInput() {
116
+ const raw = this.inputText
117
+ if (raw === '' || raw === '-') {
118
+ this.$emit('input', null)
119
+ this.$emit('update:modelValue', null)
120
+ this.$emit('change', null)
121
+ return
122
+ }
123
+ const num = parseFloat(raw)
124
+ if (!isNaN(num)) {
125
+ const committed = this.clamp(this.toPrecision(num))
126
+ this.$emit('input', committed)
127
+ this.$emit('update:modelValue', committed)
128
+ this.$emit('change', committed)
129
+ this.inputText = committed.toFixed(this.effectivePrecision)
130
+ }
131
+ },
132
+ onInput(e) {
133
+ const sanitized = this.sanitize(e.target.value)
134
+ if (sanitized !== e.target.value) {
135
+ const pos = Math.max(0, e.target.selectionStart - (e.target.value.length - sanitized.length))
136
+ e.target.value = sanitized
137
+ try {
138
+ e.target.setSelectionRange(pos, pos)
139
+ } catch (err) {
140
+ console.error(err)
141
+ }
142
+ }
143
+ this.inputText = sanitized
144
+ if (sanitized === '' || sanitized === '-') return
145
+ const num = parseFloat(sanitized)
146
+ if (!isNaN(num)) {
147
+ this.$emit('input', this.toPrecision(num))
148
+ this.$emit('update:modelValue', this.toPrecision(num))
149
+ }
150
+ },
151
+ onFocus(e) {
152
+ this.focused = true
153
+ const v = this.currentValue
154
+ this.inputText =
155
+ v !== null && v !== undefined ? parseFloat(Number(v).toFixed(this.effectivePrecision)).toString() : ''
156
+ this.$emit('focus', this.currentValue, e)
157
+ },
158
+ onBlur(e) {
159
+ this.focused = false
160
+ this.commitInput()
161
+ this.$emit('blur', this.currentValue, e)
162
+ },
163
+ onKeydown(e) {
164
+ if (e.key === 'ArrowUp') {
165
+ e.preventDefault()
166
+ const base = this.currentValue !== null && this.currentValue !== undefined ? Number(this.currentValue) : 0
167
+ const next = this.clamp(this.toPrecision(base + this.step))
168
+ this.$emit('input', next)
169
+ this.$emit('update:modelValue', next)
170
+ this.$emit('change', next)
171
+ this.inputText = String(next)
172
+ } else if (e.key === 'ArrowDown') {
173
+ e.preventDefault()
174
+ const base = this.currentValue !== null && this.currentValue !== undefined ? Number(this.currentValue) : 0
175
+ const next = this.clamp(this.toPrecision(base - this.step))
176
+ this.$emit('input', next)
177
+ this.$emit('update:modelValue', next)
178
+ this.$emit('change', next)
179
+ this.inputText = String(next)
180
+ } else if (e.key === 'Enter') {
181
+ this.commitInput()
182
+ this.$emit('pressEnter', this.currentValue, e)
183
+ }
184
+ }
185
+ }
186
+ }
187
+ </script>
188
+
189
+ <style src="./input-number.css" scoped></style>
@@ -0,0 +1,130 @@
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
+ }
@@ -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'