orio-ui 1.7.4 → 1.8.0
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/dist/module.json +1 -1
- package/dist/runtime/assets/css/mixins.css +0 -0
- package/dist/runtime/components/Button.vue +20 -0
- package/dist/runtime/components/ControlElement.vue +5 -0
- package/dist/runtime/components/Input.vue +7 -6
- package/dist/runtime/components/NumberInput.vue +136 -0
- package/dist/runtime/components/Popover.vue +2 -1
- package/dist/runtime/components/Selector.vue +1 -1
- package/dist/runtime/composables/index.d.ts +1 -0
- package/dist/runtime/composables/index.js +1 -0
- package/dist/runtime/composables/useDecimalFormatter.d.ts +11 -0
- package/dist/runtime/composables/useDecimalFormatter.js +40 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
File without changes
|
|
@@ -25,12 +25,28 @@ const isIconOnly = computed(() => {
|
|
|
25
25
|
|
|
26
26
|
const emit = defineEmits<{
|
|
27
27
|
(e: "click", event: PointerEvent): void;
|
|
28
|
+
(e: "mousedown", event: MouseEvent): void;
|
|
29
|
+
(e: "mouseup", event: MouseEvent): void;
|
|
30
|
+
(e: "mouseleave", event: MouseEvent): void;
|
|
28
31
|
}>();
|
|
29
32
|
|
|
30
33
|
function click(event: PointerEvent) {
|
|
31
34
|
if (loading.value || disabled.value) return;
|
|
32
35
|
emit("click", event);
|
|
33
36
|
}
|
|
37
|
+
|
|
38
|
+
function onMousedown(event: MouseEvent) {
|
|
39
|
+
if (loading.value || disabled.value) return;
|
|
40
|
+
emit("mousedown", event);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function onMouseup(event: MouseEvent) {
|
|
44
|
+
emit("mouseup", event);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function onMouseleave(event: MouseEvent) {
|
|
48
|
+
emit("mouseleave", event);
|
|
49
|
+
}
|
|
34
50
|
</script>
|
|
35
51
|
|
|
36
52
|
<template>
|
|
@@ -40,6 +56,9 @@ function click(event: PointerEvent) {
|
|
|
40
56
|
:class="[variant, 'gradient-hover', { 'icon-only': isIconOnly }]"
|
|
41
57
|
:disabled
|
|
42
58
|
@click="click"
|
|
59
|
+
@mousedown="onMousedown"
|
|
60
|
+
@mouseup="onMouseup"
|
|
61
|
+
@mouseleave="onMouseleave"
|
|
43
62
|
>
|
|
44
63
|
<orio-loading-spinner v-if="loading" />
|
|
45
64
|
<slot v-else name="icon">
|
|
@@ -68,6 +87,7 @@ button {
|
|
|
68
87
|
button.icon-only {
|
|
69
88
|
padding: 0;
|
|
70
89
|
border-radius: 50%;
|
|
90
|
+
line-height: 0;
|
|
71
91
|
}
|
|
72
92
|
button:disabled, button:disabled:hover {
|
|
73
93
|
background-color: var(--color-accent-soft-base);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
(e: "input", value: string): void;
|
|
4
|
-
}>();
|
|
5
|
-
|
|
6
|
-
const text = defineModel<string>({ default: "" });
|
|
2
|
+
const modelValue = defineModel<string>({ default: "" });
|
|
7
3
|
</script>
|
|
8
4
|
|
|
9
5
|
<template>
|
|
10
6
|
<orio-control-element v-bind="$attrs">
|
|
11
|
-
<input
|
|
7
|
+
<input
|
|
8
|
+
v-bind="$attrs"
|
|
9
|
+
v-model="modelValue"
|
|
10
|
+
type="text"
|
|
11
|
+
class="text-input"
|
|
12
|
+
/>
|
|
12
13
|
</orio-control-element>
|
|
13
14
|
</template>
|
|
14
15
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, toRefs } from "vue";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
decimalPlaces?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
12
|
+
min: undefined,
|
|
13
|
+
max: undefined,
|
|
14
|
+
step: 1,
|
|
15
|
+
decimalPlaces: 0,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const { min, max, step, decimalPlaces } = toRefs(props);
|
|
19
|
+
|
|
20
|
+
const modelValue = defineModel<number>({ default: 0 });
|
|
21
|
+
|
|
22
|
+
const interval = ref<number | null>(null);
|
|
23
|
+
const timeout = ref<number | null>(null);
|
|
24
|
+
|
|
25
|
+
function pressAndHold(callback: () => void) {
|
|
26
|
+
callback();
|
|
27
|
+
timeout.value = window.setTimeout(() => {
|
|
28
|
+
interval.value = window.setInterval(callback, 50);
|
|
29
|
+
}, 500);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function stop() {
|
|
33
|
+
if (timeout.value) {
|
|
34
|
+
window.clearTimeout(timeout.value);
|
|
35
|
+
timeout.value = null;
|
|
36
|
+
}
|
|
37
|
+
if (interval.value) {
|
|
38
|
+
window.clearInterval(interval.value);
|
|
39
|
+
interval.value = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function setValidatedValue(value: number) {
|
|
44
|
+
let finalValue = value;
|
|
45
|
+
|
|
46
|
+
if (Number.isFinite(max.value) && finalValue > (max.value as number)) {
|
|
47
|
+
finalValue = max.value as number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (Number.isFinite(min.value) && finalValue < (min.value as number)) {
|
|
51
|
+
finalValue = min.value as number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
finalValue = Number((finalValue ?? 0).toFixed(decimalPlaces.value));
|
|
55
|
+
|
|
56
|
+
modelValue.value = finalValue;
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<orio-control-element v-bind="$attrs">
|
|
62
|
+
<div class="wrapper">
|
|
63
|
+
<input
|
|
64
|
+
v-bind="$attrs"
|
|
65
|
+
v-model="modelValue"
|
|
66
|
+
type="number"
|
|
67
|
+
class="number-input"
|
|
68
|
+
/>
|
|
69
|
+
<div class="controls">
|
|
70
|
+
<orio-button
|
|
71
|
+
appearance="minimal"
|
|
72
|
+
icon="chevron-up"
|
|
73
|
+
:disabled="Number.isFinite(max) && modelValue >= (max as number)"
|
|
74
|
+
@mousedown="pressAndHold(() => setValidatedValue(modelValue + step))"
|
|
75
|
+
@mouseup="stop"
|
|
76
|
+
@mouseleave="stop"
|
|
77
|
+
/>
|
|
78
|
+
<orio-button
|
|
79
|
+
appearance="minimal"
|
|
80
|
+
icon="chevron-down"
|
|
81
|
+
:disabled="Number.isFinite(min) && modelValue <= (min as number)"
|
|
82
|
+
@mousedown="pressAndHold(() => setValidatedValue(modelValue - step))"
|
|
83
|
+
@mouseup="stop"
|
|
84
|
+
@mouseleave="stop"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</orio-control-element>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<style scoped>
|
|
92
|
+
.number-input {
|
|
93
|
+
width: 100%;
|
|
94
|
+
padding: 0.5rem 0.75rem;
|
|
95
|
+
border: 1px solid var(--color-border);
|
|
96
|
+
border-radius: var(--border-radius-md);
|
|
97
|
+
font-size: 1rem;
|
|
98
|
+
line-height: 1.5;
|
|
99
|
+
color: var(--color-text);
|
|
100
|
+
background-color: var(--color-bg);
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
103
|
+
}
|
|
104
|
+
.number-input::placeholder {
|
|
105
|
+
color: var(--color-muted);
|
|
106
|
+
}
|
|
107
|
+
.number-input:hover {
|
|
108
|
+
border-color: var(--color-accent);
|
|
109
|
+
}
|
|
110
|
+
.number-input:focus {
|
|
111
|
+
border-color: var(--color-accent);
|
|
112
|
+
outline: none;
|
|
113
|
+
}
|
|
114
|
+
.number-input:disabled {
|
|
115
|
+
background-color: var(--color-surface);
|
|
116
|
+
color: var(--color-muted);
|
|
117
|
+
cursor: not-allowed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.wrapper {
|
|
121
|
+
position: relative;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.controls {
|
|
125
|
+
position: absolute;
|
|
126
|
+
height: 100%;
|
|
127
|
+
right: 3px;
|
|
128
|
+
top: 0;
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
justify-content: space-around;
|
|
132
|
+
}
|
|
133
|
+
.controls div {
|
|
134
|
+
margin: 0.25rem;
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<div ref="trigger"
|
|
3
|
+
<div ref="trigger">
|
|
4
4
|
<slot :toggle="togglePopover" />
|
|
5
5
|
</div>
|
|
6
6
|
|
|
@@ -202,6 +202,7 @@ async function updateRects() {
|
|
|
202
202
|
async function togglePopover(force: boolean | null = null) {
|
|
203
203
|
if (props.disabled) return;
|
|
204
204
|
showPopover.value = force !== null ? force : !showPopover.value;
|
|
205
|
+
|
|
205
206
|
if (!showPopover.value) return;
|
|
206
207
|
|
|
207
208
|
await nextTick();
|
|
@@ -95,7 +95,7 @@ const selectorAttrs = computed(() => ({ getOptionKey, getOptionLabel }));
|
|
|
95
95
|
<orio-popover position="bottom-right" :offset="5">
|
|
96
96
|
<template #default="{ toggle }">
|
|
97
97
|
<slot name="trigger" :toggle>
|
|
98
|
-
<div class="selector-trigger">
|
|
98
|
+
<div class="selector-trigger" @click="toggle()">
|
|
99
99
|
<slot
|
|
100
100
|
name="trigger-content"
|
|
101
101
|
:toggle
|
|
@@ -2,3 +2,4 @@ export { useApi, type ApiOptions, type RequestBody, type RequestMethod, } from "
|
|
|
2
2
|
export { useFuzzySearch } from "./useFuzzySearch.js";
|
|
3
3
|
export { useModal, type ModalProps, type OriginRect } from "./useModal.js";
|
|
4
4
|
export { useTheme } from "./useTheme.js";
|
|
5
|
+
export { useDecimalFormatter } from "./useDecimalFormatter.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type DecimalFormatOptions = {
|
|
2
|
+
locale?: string;
|
|
3
|
+
decimals?: number;
|
|
4
|
+
minimumFractionDigits?: number;
|
|
5
|
+
maximumFractionDigits?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function useDecimalFormatter(): {
|
|
8
|
+
toNumber: (input: string | number) => number | null;
|
|
9
|
+
formatDecimal: (input: string | number, options?: DecimalFormatOptions) => string | null;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function getSystemLocale() {
|
|
2
|
+
if (typeof navigator === "undefined") return "de-DE";
|
|
3
|
+
return navigator.languages?.[0] ?? navigator.language;
|
|
4
|
+
}
|
|
5
|
+
export function useDecimalFormatter() {
|
|
6
|
+
function toNumber(input) {
|
|
7
|
+
if (typeof input === "number") {
|
|
8
|
+
return Number.isFinite(input) ? input : null;
|
|
9
|
+
}
|
|
10
|
+
if (typeof input !== "string") return null;
|
|
11
|
+
const cleaned = input.replace(/[^\d.,-]/g, "").trim();
|
|
12
|
+
if (!cleaned) return null;
|
|
13
|
+
const lastComma = cleaned.lastIndexOf(",");
|
|
14
|
+
const lastDot = cleaned.lastIndexOf(".");
|
|
15
|
+
let normalized = cleaned;
|
|
16
|
+
if (lastComma > lastDot) {
|
|
17
|
+
normalized = cleaned.replace(/\./g, "").replace(",", ".");
|
|
18
|
+
} else {
|
|
19
|
+
normalized = cleaned.replace(/,/g, "");
|
|
20
|
+
}
|
|
21
|
+
const result = Number(normalized);
|
|
22
|
+
return Number.isFinite(result) ? result : null;
|
|
23
|
+
}
|
|
24
|
+
function formatDecimal(input, options = {}) {
|
|
25
|
+
const locale = options.locale ?? getSystemLocale();
|
|
26
|
+
const decimals = options.decimals ?? 2;
|
|
27
|
+
const minimumFractionDigits = options.minimumFractionDigits ?? decimals;
|
|
28
|
+
const maximumFractionDigits = options.maximumFractionDigits ?? decimals;
|
|
29
|
+
const value = toNumber(input);
|
|
30
|
+
if (value === null) return null;
|
|
31
|
+
return new Intl.NumberFormat(locale, {
|
|
32
|
+
minimumFractionDigits,
|
|
33
|
+
maximumFractionDigits
|
|
34
|
+
}).format(value);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
toNumber,
|
|
38
|
+
formatDecimal
|
|
39
|
+
};
|
|
40
|
+
}
|