orio-ui 1.10.4 → 1.11.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/README.md CHANGED
@@ -111,12 +111,13 @@ function handleClick() {
111
111
 
112
112
  - **Upload** - File upload component
113
113
 
114
- ### Composables (7)
114
+ ### Composables (8)
115
115
 
116
116
  - **useTheme** - Theme and color mode management
117
117
  - **useModal** - Modal state with animation origin tracking
118
118
  - **useFuzzySearch** - Fuzzy search powered by Fuse.js
119
119
  - **useApi** - Type-safe API request wrapper
120
+ - **useValidation** - Form validation with error handling
120
121
  - **useDecimalFormatter** - Number formatting utilities
121
122
  - **usePressAndHold** - Press and hold interaction handler
122
123
  - **useSound** - Audio playback with CDN-hosted sounds
@@ -184,7 +185,7 @@ orio-ui/
184
185
  ├── src/
185
186
  │ ├── runtime/
186
187
  │ │ ├── components/ # 26 Vue components
187
- │ │ ├── composables/ # 7 composables
188
+ │ │ ├── composables/ # 8 composables
188
189
  │ │ ├── assets/css/ # Theme CSS files
189
190
  │ │ └── utils/ # Icon registry
190
191
  │ └── module.ts # Nuxt Module definition
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0 || ^4.0.0"
6
6
  },
7
- "version": "1.10.4",
7
+ "version": "1.11.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -4,19 +4,25 @@ export interface ControlProps {
4
4
  * Minimal will reset margin and remove border and box shadow from every element inside the slot
5
5
  */
6
6
  appearance?: "normal" | "minimal";
7
+ /**
8
+ * Error message to display below the control
9
+ */
10
+ error?: string | null;
7
11
  }
8
12
 
9
13
  withDefaults(defineProps<ControlProps>(), {
10
14
  appearance: "normal",
15
+ error: null,
11
16
  });
12
17
  </script>
13
18
 
14
19
  <template>
15
- <div class="control" :class="[appearance]">
20
+ <div class="control" :class="[appearance, { 'has-error': error }]">
16
21
  <label v-if="$attrs.label" class="control-label">{{ $attrs.label }}</label>
17
22
  <div class="slot-wrapper" v-bind="$attrs">
18
23
  <slot />
19
24
  </div>
25
+ <span v-if="error" class="control-error">{{ error }}</span>
20
26
  </div>
21
27
  </template>
22
28
 
@@ -30,6 +36,13 @@ withDefaults(defineProps<ControlProps>(), {
30
36
  .control .control-label {
31
37
  user-select: none;
32
38
  }
39
+ .control .control-error {
40
+ color: var(--color-danger);
41
+ font-size: 0.875rem;
42
+ }
43
+ .control.has-error .slot-wrapper :deep(*) {
44
+ border-color: var(--color-danger);
45
+ }
33
46
  .control.minimal {
34
47
  margin: 0;
35
48
  }
@@ -5,3 +5,4 @@ export { useTheme } from "./useTheme.js";
5
5
  export { useDecimalFormatter } from "./useDecimalFormatter.js";
6
6
  export { usePressAndHold } from "./usePressAndHold.js";
7
7
  export { useSound, type SoundOptions } from "./useSound.js";
8
+ export { useValidation, isFilled, isEmail, type ValidationRule, } from "./useValidation.js";
@@ -7,3 +7,8 @@ export { useTheme } from "./useTheme.js";
7
7
  export { useDecimalFormatter } from "./useDecimalFormatter.js";
8
8
  export { usePressAndHold } from "./usePressAndHold.js";
9
9
  export { useSound } from "./useSound.js";
10
+ export {
11
+ useValidation,
12
+ isFilled,
13
+ isEmail
14
+ } from "./useValidation.js";
@@ -0,0 +1,15 @@
1
+ import { type MaybeRef } from "vue";
2
+ export interface ValidationRule {
3
+ model: MaybeRef<any>;
4
+ id: string;
5
+ validator: (model: MaybeRef<any>) => boolean;
6
+ message?: string;
7
+ }
8
+ export declare function isFilled(model: MaybeRef<string | []>): boolean;
9
+ export declare function isEmail(model: MaybeRef<string>): boolean;
10
+ export declare function useValidation(rules: ValidationRule[]): {
11
+ checkValidity: () => boolean;
12
+ errors: Record<string, string | null>;
13
+ clearError: (id: string) => void;
14
+ clearAllErrors: () => void;
15
+ };
@@ -0,0 +1,42 @@
1
+ import { reactive, isRef } from "vue";
2
+ export function isFilled(model) {
3
+ return isRef(model) ? !!model.value.length : !!model.length;
4
+ }
5
+ export function isEmail(model) {
6
+ const value = isRef(model) ? model.value : model;
7
+ if (!value) return true;
8
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
9
+ return emailRegex.test(value);
10
+ }
11
+ export function useValidation(rules) {
12
+ const errors = reactive({});
13
+ function validate({
14
+ model,
15
+ id,
16
+ validator,
17
+ message
18
+ }) {
19
+ if (!validator(model)) {
20
+ if (!errors[id]) {
21
+ errors[id] = message || "Error on this field";
22
+ }
23
+ document.getElementById(id)?.scrollIntoView({
24
+ behavior: "smooth",
25
+ block: "center"
26
+ });
27
+ return false;
28
+ }
29
+ return true;
30
+ }
31
+ function checkValidity() {
32
+ clearAllErrors();
33
+ return rules.reduceRight((valid, rule) => validate(rule) && valid, true);
34
+ }
35
+ function clearError(id) {
36
+ errors[id] = null;
37
+ }
38
+ function clearAllErrors() {
39
+ Object.keys(errors).forEach((key) => errors[key] = null);
40
+ }
41
+ return { checkValidity, errors, clearError, clearAllErrors };
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orio-ui",
3
- "version": "1.10.4",
3
+ "version": "1.11.0",
4
4
  "description": "Modern Nuxt component library with theme support",
5
5
  "type": "module",
6
6
  "main": "./dist/module.mjs",