windly-ui 1.0.9 → 1.1.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 (63) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/Alert.d.vue.ts +18 -0
  3. package/dist/runtime/components/Alert.vue +21 -3
  4. package/dist/runtime/components/Alert.vue.d.ts +18 -0
  5. package/dist/runtime/components/Avatar.d.vue.ts +5 -5
  6. package/dist/runtime/components/Avatar.vue.d.ts +5 -5
  7. package/dist/runtime/components/Badge.d.vue.ts +5 -5
  8. package/dist/runtime/components/Badge.vue.d.ts +5 -5
  9. package/dist/runtime/components/Breadcrumbs.vue +2 -2
  10. package/dist/runtime/components/Button.d.vue.ts +5 -5
  11. package/dist/runtime/components/Button.vue +5 -5
  12. package/dist/runtime/components/Button.vue.d.ts +5 -5
  13. package/dist/runtime/components/Card.d.vue.ts +27 -0
  14. package/dist/runtime/components/Card.vue +12 -8
  15. package/dist/runtime/components/Card.vue.d.ts +27 -0
  16. package/dist/runtime/components/Checkbox.d.vue.ts +14 -5
  17. package/dist/runtime/components/Checkbox.vue +11 -4
  18. package/dist/runtime/components/Checkbox.vue.d.ts +14 -5
  19. package/dist/runtime/components/Chip.d.vue.ts +5 -5
  20. package/dist/runtime/components/Chip.vue.d.ts +5 -5
  21. package/dist/runtime/components/CodeBlock.vue +50 -8
  22. package/dist/runtime/components/Date.d.vue.ts +22 -4
  23. package/dist/runtime/components/Date.vue +39 -15
  24. package/dist/runtime/components/Date.vue.d.ts +22 -4
  25. package/dist/runtime/components/Dialog.d.vue.ts +7 -9
  26. package/dist/runtime/components/Dialog.vue +23 -4
  27. package/dist/runtime/components/Dialog.vue.d.ts +7 -9
  28. package/dist/runtime/components/Divider.vue +1 -1
  29. package/dist/runtime/components/Dropdown.d.vue.ts +2 -2
  30. package/dist/runtime/components/Dropdown.vue +71 -23
  31. package/dist/runtime/components/Dropdown.vue.d.ts +2 -2
  32. package/dist/runtime/components/FileUploader.d.vue.ts +1 -1
  33. package/dist/runtime/components/FileUploader.vue +11 -9
  34. package/dist/runtime/components/FileUploader.vue.d.ts +1 -1
  35. package/dist/runtime/components/Image.d.vue.ts +11 -1
  36. package/dist/runtime/components/Image.vue +15 -0
  37. package/dist/runtime/components/Image.vue.d.ts +11 -1
  38. package/dist/runtime/components/Input.d.vue.ts +5 -1
  39. package/dist/runtime/components/Input.vue +53 -14
  40. package/dist/runtime/components/Input.vue.d.ts +5 -1
  41. package/dist/runtime/components/Radio.d.vue.ts +2 -2
  42. package/dist/runtime/components/Radio.vue +9 -7
  43. package/dist/runtime/components/Radio.vue.d.ts +2 -2
  44. package/dist/runtime/components/ScrollArea.vue +4 -0
  45. package/dist/runtime/components/Select.d.vue.ts +1 -1
  46. package/dist/runtime/components/Select.vue +11 -9
  47. package/dist/runtime/components/Select.vue.d.ts +1 -1
  48. package/dist/runtime/components/Tabs.d.vue.ts +8 -1
  49. package/dist/runtime/components/Tabs.vue +21 -6
  50. package/dist/runtime/components/Tabs.vue.d.ts +8 -1
  51. package/dist/runtime/components/Textarea.d.vue.ts +39 -185
  52. package/dist/runtime/components/Textarea.vue +95 -72
  53. package/dist/runtime/components/Textarea.vue.d.ts +39 -185
  54. package/dist/runtime/components/Toggle.d.vue.ts +9 -3
  55. package/dist/runtime/components/Toggle.vue +70 -122
  56. package/dist/runtime/components/Toggle.vue.d.ts +9 -3
  57. package/dist/runtime/components/Tooltip.vue +3 -0
  58. package/dist/runtime/components/uiProps.d.ts +2 -2
  59. package/dist/runtime/components/uiProps.js +2 -2
  60. package/dist/runtime/components/useUiClasses.d.ts +3 -1
  61. package/dist/runtime/components/useUiClasses.js +7 -5
  62. package/dist/runtime/docs/index.vue +163 -72
  63. package/package.json +1 -1
@@ -1,10 +1,6 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
4
- type __VLS_WithSlots<T, S> = T & (new () => {
5
- $slots: S;
6
- });
7
- declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<import("vue").ExtractPropTypes<{
8
4
  isOpen: {
9
5
  type: BooleanConstructor;
10
6
  required: true;
@@ -57,7 +53,9 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
57
53
  height: string;
58
54
  width: string;
59
55
  persistent: boolean;
60
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
61
- type __VLS_Slots = {
62
- default?: ((props: {}) => any) | undefined;
63
- };
56
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>, {
57
+ default?: (props: {}) => any;
58
+ }>;
59
+ type __VLS_WithSlots<T, S> = T & (new () => {
60
+ $slots: S;
61
+ });
@@ -1,5 +1,10 @@
1
+ <script>
2
+ const openStack = [];
3
+ let nextInstanceId = 0;
4
+ </script>
5
+
1
6
  <script setup>
2
- import { computed, watch, onBeforeUnmount } from "vue";
7
+ import { computed, watch, onBeforeUnmount, ref } from "vue";
3
8
  import { useRouter } from "vue-router";
4
9
  const props = defineProps({
5
10
  isOpen: {
@@ -26,15 +31,26 @@ const props = defineProps({
26
31
  });
27
32
  const emit = defineEmits(["update:isOpen"]);
28
33
  const router = useRouter();
34
+ const instanceId = ++nextInstanceId;
35
+ const zIndex = ref(50);
29
36
  const close = () => emit("update:isOpen", false);
30
37
  const handleOutsideClick = () => {
31
38
  if (!props.persistent) close();
32
39
  };
40
+ const isTopMost = () => openStack[openStack.length - 1] === instanceId;
33
41
  const handleEscKey = (event) => {
34
- if (event.key === "Escape" && !props.persistent) {
42
+ if (event.key === "Escape" && !props.persistent && isTopMost()) {
35
43
  close();
36
44
  }
37
45
  };
46
+ const registerOpen = () => {
47
+ if (!openStack.includes(instanceId)) openStack.push(instanceId);
48
+ zIndex.value = 50 + openStack.indexOf(instanceId) * 10;
49
+ };
50
+ const registerClose = () => {
51
+ const index = openStack.indexOf(instanceId);
52
+ if (index !== -1) openStack.splice(index, 1);
53
+ };
38
54
  const positionClasses = computed(() => {
39
55
  switch (props.position) {
40
56
  case "top":
@@ -53,8 +69,10 @@ watch(
53
69
  () => props.isOpen,
54
70
  (open) => {
55
71
  if (open) {
72
+ registerOpen();
56
73
  document.addEventListener("keydown", handleEscKey);
57
74
  } else {
75
+ registerClose();
58
76
  document.removeEventListener("keydown", handleEscKey);
59
77
  }
60
78
  }
@@ -65,14 +83,15 @@ const removeGuard = router.beforeEach((to, from, next) => {
65
83
  });
66
84
  onBeforeUnmount(() => {
67
85
  document.removeEventListener("keydown", handleEscKey);
86
+ registerClose();
68
87
  removeGuard?.();
69
88
  });
70
89
  </script>
71
90
 
72
91
  <template>
73
92
  <transition name="fade">
74
- <div v-if="isOpen" class="fixed inset-0 z-50 flex bg-black bg-opacity-60" role="dialog" aria-modal="true"
75
- @click.self="handleOutsideClick" :class="positionClasses">
93
+ <div v-if="isOpen" class="fixed inset-0 flex bg-black bg-opacity-60" role="dialog" aria-modal="true"
94
+ @click.self="handleOutsideClick" :class="positionClasses" :style="{ zIndex }">
76
95
  <div :style="{ width: width, height: height }">
77
96
  <slot />
78
97
  </div>
@@ -1,10 +1,6 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
4
- type __VLS_WithSlots<T, S> = T & (new () => {
5
- $slots: S;
6
- });
7
- declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<import("vue").ExtractPropTypes<{
8
4
  isOpen: {
9
5
  type: BooleanConstructor;
10
6
  required: true;
@@ -57,7 +53,9 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
57
53
  height: string;
58
54
  width: string;
59
55
  persistent: boolean;
60
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
61
- type __VLS_Slots = {
62
- default?: ((props: {}) => any) | undefined;
63
- };
56
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>, {
57
+ default?: (props: {}) => any;
58
+ }>;
59
+ type __VLS_WithSlots<T, S> = T & (new () => {
60
+ $slots: S;
61
+ });
@@ -38,7 +38,7 @@ const dividerClass = computed(() => [
38
38
  props.vertical ? "h-full border-l" : "w-full border-t",
39
39
  props.type === "dashed" ? "border-dashed" : "",
40
40
  props.type === "dotted" ? "border-dotted" : "",
41
- props.inset ? "mx-4" : "",
41
+ props.inset ? props.vertical ? "my-4" : "mx-4" : "",
42
42
  !isHex(props.color) ? `border-${props.color}` : ""
43
43
  ]);
44
44
  </script>
@@ -9,11 +9,11 @@ type __VLS_Props = {
9
9
  borderColor?: string;
10
10
  flat?: boolean;
11
11
  };
12
- declare var __VLS_1: {}, __VLS_9: {};
12
+ declare var __VLS_1: {}, __VLS_15: {};
13
13
  type __VLS_Slots = {} & {
14
14
  trigger?: (props: typeof __VLS_1) => any;
15
15
  } & {
16
- menu?: (props: typeof __VLS_9) => any;
16
+ menu?: (props: typeof __VLS_15) => any;
17
17
  };
18
18
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
19
19
  flat: boolean;
@@ -4,6 +4,7 @@ defineOptions({
4
4
  });
5
5
  import {
6
6
  computed,
7
+ nextTick,
7
8
  onBeforeUnmount,
8
9
  onMounted,
9
10
  ref
@@ -19,34 +20,79 @@ const props = defineProps({
19
20
  });
20
21
  const isOpen = ref(false);
21
22
  const dropdownRef = ref(null);
23
+ const menuRef = ref(null);
24
+ const menuPositionStyle = ref({});
22
25
  const isHex = (val) => {
23
26
  return val.startsWith("#") || val.startsWith("rgb") || val.startsWith("hsl");
24
27
  };
25
- const toggle = () => {
28
+ function updatePosition() {
29
+ const trigger = dropdownRef.value;
30
+ if (!trigger) return;
31
+ const rect = trigger.getBoundingClientRect();
32
+ const viewportWidth = window.innerWidth;
33
+ const viewportHeight = window.innerHeight;
34
+ const gap = 8;
35
+ const style = {
36
+ position: "fixed",
37
+ zIndex: "9999"
38
+ };
39
+ if (props.align === "right") {
40
+ style.right = `${Math.max(viewportWidth - rect.right, 0)}px`;
41
+ } else {
42
+ style.left = `${Math.max(rect.left, 0)}px`;
43
+ }
44
+ const estimatedMenuHeight = menuRef.value?.offsetHeight ?? 0;
45
+ const fitsBelow = rect.bottom + gap + estimatedMenuHeight <= viewportHeight;
46
+ if (fitsBelow) {
47
+ style.top = `${rect.bottom + gap}px`;
48
+ } else {
49
+ style.bottom = `${Math.max(viewportHeight - rect.top + gap, 0)}px`;
50
+ }
51
+ menuPositionStyle.value = style;
52
+ }
53
+ const toggle = async () => {
26
54
  isOpen.value = !isOpen.value;
55
+ if (isOpen.value) {
56
+ await nextTick();
57
+ updatePosition();
58
+ }
27
59
  };
28
60
  const close = () => {
29
61
  isOpen.value = false;
30
62
  };
31
63
  const handleClickOutside = (event) => {
32
- const target = event.target;
33
- if (dropdownRef.value && !dropdownRef.value.contains(
34
- target
35
- )) {
64
+ const path = event.composedPath();
65
+ const clickedTrigger = dropdownRef.value && path.includes(dropdownRef.value);
66
+ const clickedMenu = menuRef.value && path.includes(menuRef.value);
67
+ if (!clickedTrigger && !clickedMenu) {
68
+ close();
69
+ }
70
+ };
71
+ const handleEscape = (event) => {
72
+ if (event.key === "Escape" && isOpen.value) {
36
73
  close();
37
74
  }
38
75
  };
76
+ const handleReposition = () => {
77
+ if (isOpen.value) updatePosition();
78
+ };
39
79
  onMounted(() => {
40
80
  document.addEventListener(
41
81
  "click",
42
82
  handleClickOutside
43
83
  );
84
+ document.addEventListener("keydown", handleEscape);
85
+ window.addEventListener("resize", handleReposition);
86
+ window.addEventListener("scroll", handleReposition, true);
44
87
  });
45
88
  onBeforeUnmount(() => {
46
89
  document.removeEventListener(
47
90
  "click",
48
91
  handleClickOutside
49
92
  );
93
+ document.removeEventListener("keydown", handleEscape);
94
+ window.removeEventListener("resize", handleReposition);
95
+ window.removeEventListener("scroll", handleReposition, true);
50
96
  });
51
97
  const roundedClass = computed(
52
98
  () => {
@@ -62,8 +108,7 @@ const roundedClass = computed(
62
108
  }
63
109
  );
64
110
  const dropdownClasses = computed(() => [
65
- "absolute mt-2 z-50 transition-all overflow-hidden",
66
- props.align === "right" ? "right-0" : "left-0",
111
+ "transition-all overflow-hidden",
67
112
  props.width,
68
113
  roundedClass.value,
69
114
  props.bordered && !isHex(props.borderColor) ? `border border-${props.borderColor}` : "",
@@ -75,7 +120,7 @@ const dropdownStyle = computed(
75
120
  if (props.bordered && isHex(props.borderColor)) {
76
121
  style.border = `1px solid ${props.borderColor}`;
77
122
  }
78
- return style;
123
+ return { ...style, ...menuPositionStyle.value };
79
124
  }
80
125
  );
81
126
  const menuClasses = computed(
@@ -93,32 +138,35 @@ const menuClasses = computed(
93
138
  class="relative inline-block"
94
139
  >
95
140
  <!-- Trigger -->
96
- <div @click="toggle">
141
+ <div aria-haspopup="true" :aria-expanded="isOpen" @click="toggle">
97
142
  <slot name="trigger" />
98
143
  </div>
99
144
 
100
145
  <!-- Dropdown -->
101
- <transition
102
- name="fade-slide"
103
- >
104
- <div
105
- v-if="isOpen"
106
- :class="
146
+ <Teleport to="body">
147
+ <transition
148
+ name="fade-slide"
149
+ >
150
+ <div
151
+ v-if="isOpen"
152
+ ref="menuRef"
153
+ :class="
107
154
  dropdownClasses
108
155
  "
109
- :style="
156
+ :style="
110
157
  dropdownStyle
111
158
  "
112
- >
113
- <div
114
- :class="
159
+ >
160
+ <div
161
+ :class="
115
162
  menuClasses
116
163
  "
117
- >
118
- <slot name="menu" />
164
+ >
165
+ <slot name="menu" />
166
+ </div>
119
167
  </div>
120
- </div>
121
- </transition>
168
+ </transition>
169
+ </Teleport>
122
170
  </div>
123
171
  </template>
124
172
 
@@ -9,11 +9,11 @@ type __VLS_Props = {
9
9
  borderColor?: string;
10
10
  flat?: boolean;
11
11
  };
12
- declare var __VLS_1: {}, __VLS_9: {};
12
+ declare var __VLS_1: {}, __VLS_15: {};
13
13
  type __VLS_Slots = {} & {
14
14
  trigger?: (props: typeof __VLS_1) => any;
15
15
  } & {
16
- menu?: (props: typeof __VLS_9) => any;
16
+ menu?: (props: typeof __VLS_15) => any;
17
17
  };
18
18
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
19
19
  flat: boolean;
@@ -47,8 +47,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
47
47
  multiple: boolean;
48
48
  width: string;
49
49
  rounded: Rounded;
50
- readonly: boolean;
51
50
  hint: string;
51
+ readonly: boolean;
52
52
  fileType: string;
53
53
  outlined: boolean;
54
54
  borderless: boolean;
@@ -35,6 +35,14 @@ const files = computed({
35
35
  )
36
36
  });
37
37
  const isHex = (val) => val.startsWith("#");
38
+ function supportsWidthValue(value) {
39
+ if (typeof CSS !== "undefined" && typeof CSS.supports === "function") {
40
+ return CSS.supports("width", value);
41
+ }
42
+ return /^-?\d*\.?\d+(px|%|rem|em|vh|vw|vmin|vmax|ch|ex|cm|mm|in|pt|pc)$/.test(
43
+ value.trim()
44
+ ) || /^(calc|var|min|max|clamp)\(/.test(value.trim());
45
+ }
38
46
  const roundedMap = {
39
47
  none: "rounded-none",
40
48
  sm: "rounded-sm",
@@ -88,12 +96,9 @@ const computedClass = computed(() => {
88
96
  classes += " text-base";
89
97
  }
90
98
  if (props.color && !isHex(props.color)) {
91
- classes += ` border-${props.color} focus:ring-${props.color}`;
99
+ classes += ` border-${props.color} focus:ring-0 `;
92
100
  }
93
- if (props.width && !CSS.supports(
94
- "width",
95
- props.width
96
- )) {
101
+ if (props.width && !supportsWidthValue(props.width)) {
97
102
  classes += ` w-${props.width}`;
98
103
  }
99
104
  if (!props.width) {
@@ -106,10 +111,7 @@ const computedClass = computed(() => {
106
111
  });
107
112
  const computedStyle = computed(() => {
108
113
  const styles = {};
109
- if (props.width && CSS.supports(
110
- "width",
111
- props.width
112
- )) {
114
+ if (props.width && supportsWidthValue(props.width)) {
113
115
  styles.width = props.width;
114
116
  }
115
117
  if (props.color && isHex(props.color)) {
@@ -47,8 +47,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
47
47
  multiple: boolean;
48
48
  width: string;
49
49
  rounded: Rounded;
50
- readonly: boolean;
51
50
  hint: string;
51
+ readonly: boolean;
52
52
  fileType: string;
53
53
  outlined: boolean;
54
54
  borderless: boolean;
@@ -1,4 +1,8 @@
1
- declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ overlay?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
6
  src: {
3
7
  type: StringConstructor;
4
8
  required: true;
@@ -73,5 +77,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
73
77
  fit: string;
74
78
  borderType: string;
75
79
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
80
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
76
81
  declare const _default: typeof __VLS_export;
77
82
  export default _default;
83
+ type __VLS_WithSlots<T, S> = T & {
84
+ new (): {
85
+ $slots: S;
86
+ };
87
+ };
@@ -50,7 +50,22 @@ function onError() {
50
50
  </script>
51
51
 
52
52
  <template>
53
+ <div v-if="$slots.overlay" class="relative inline-block">
54
+ <img
55
+ :src="imageSrc"
56
+ :alt="alt"
57
+ :class="imageClass"
58
+ :style="imageStyle"
59
+ @error="onError"
60
+ v-bind="$attrs"
61
+ >
62
+ <div class="absolute inset-0 pointer-events-none">
63
+ <slot name="overlay" />
64
+ </div>
65
+ </div>
66
+
53
67
  <img
68
+ v-else
54
69
  :src="imageSrc"
55
70
  :alt="alt"
56
71
  :class="imageClass"
@@ -1,4 +1,8 @@
1
- declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ overlay?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
6
  src: {
3
7
  type: StringConstructor;
4
8
  required: true;
@@ -73,5 +77,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
73
77
  fit: string;
74
78
  borderType: string;
75
79
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
80
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
76
81
  declare const _default: typeof __VLS_export;
77
82
  export default _default;
83
+ type __VLS_WithSlots<T, S> = T & {
84
+ new (): {
85
+ $slots: S;
86
+ };
87
+ };
@@ -28,6 +28,8 @@ type __VLS_Props = {
28
28
  readonly?: boolean;
29
29
  autocomplete?: string;
30
30
  validateOn?: ValidateOn;
31
+ error?: boolean;
32
+ errorMessage?: string;
31
33
  };
32
34
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
33
35
  "update:modelValue": (value: string) => any;
@@ -49,10 +51,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
49
51
  width: string;
50
52
  rounded: string;
51
53
  placeholder: string;
52
- readonly: boolean;
53
54
  hint: string;
55
+ readonly: boolean;
56
+ error: boolean;
54
57
  outlined: boolean;
55
58
  borderless: boolean;
59
+ errorMessage: string;
56
60
  case: "upper" | "lower" | "capitalize" | "normal" | null;
57
61
  allow: ValidationRule[];
58
62
  deny: ValidationRule[];
@@ -26,14 +26,16 @@ const props = defineProps({
26
26
  disabled: { type: Boolean, required: false, default: false },
27
27
  readonly: { type: Boolean, required: false, default: false },
28
28
  autocomplete: { type: String, required: false, default: "off" },
29
- validateOn: { type: String, required: false, default: "input" }
29
+ validateOn: { type: String, required: false, default: "input" },
30
+ error: { type: Boolean, required: false, default: false },
31
+ errorMessage: { type: String, required: false, default: "" }
30
32
  });
31
33
  const emit = defineEmits(["update:modelValue", "valid", "invalid"]);
32
34
  const validationError = ref("");
33
35
  const NATIVE_PATTERNS = {
34
36
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
35
37
  url: /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/\S*)?$/i,
36
- tel: /^\+?[1-9]\d{6,14}$/
38
+ tel: /^\+?[\d\s().-]{7,20}$/
37
39
  };
38
40
  const RULE_PATTERNS = {
39
41
  alphabets: /^[A-Za-z]+$/,
@@ -49,6 +51,14 @@ const RULE_PATTERNS = {
49
51
  uppercase: /^[A-Z]+$/,
50
52
  lowercase: /^[a-z]+$/
51
53
  };
54
+ const CHAR_CLASS_SOURCE = {
55
+ alphabets: "A-Za-z",
56
+ numbers: "0-9",
57
+ spaces: "\\s",
58
+ punctuation: `.,!?;:'"()\\-`,
59
+ uppercase: "A-Z",
60
+ lowercase: "a-z"
61
+ };
52
62
  function validate(value) {
53
63
  validationError.value = "";
54
64
  if (props.required && !value.trim()) {
@@ -76,7 +86,17 @@ function validate(value) {
76
86
  validationError.value = `Invalid ${props.type} format.`;
77
87
  return false;
78
88
  }
79
- for (const rule of props.allow) {
89
+ const allowCharRules = props.allow.filter((rule) => CHAR_CLASS_SOURCE[rule]);
90
+ const allowFormatRules = props.allow.filter((rule) => !CHAR_CLASS_SOURCE[rule]);
91
+ if (allowCharRules.length && value) {
92
+ const unionSource = allowCharRules.map((rule) => CHAR_CLASS_SOURCE[rule]).join("");
93
+ const unionRegex = new RegExp(`^[${unionSource}]*$`);
94
+ if (!unionRegex.test(value)) {
95
+ validationError.value = `Only ${allowCharRules.join(", ")} characters are allowed.`;
96
+ return false;
97
+ }
98
+ }
99
+ for (const rule of allowFormatRules) {
80
100
  const regex = RULE_PATTERNS[rule];
81
101
  if (regex && value && !regex.test(value)) {
82
102
  validationError.value = `Failed validation: ${rule}`;
@@ -84,8 +104,17 @@ function validate(value) {
84
104
  }
85
105
  }
86
106
  for (const rule of props.deny) {
107
+ const charSource = CHAR_CLASS_SOURCE[rule];
108
+ if (charSource) {
109
+ const presenceRegex = new RegExp(`[${charSource}]`);
110
+ if (value && presenceRegex.test(value)) {
111
+ validationError.value = `${rule} characters are not permitted.`;
112
+ return false;
113
+ }
114
+ continue;
115
+ }
87
116
  const regex = RULE_PATTERNS[rule];
88
- if (regex && regex.test(value)) {
117
+ if (regex && value && regex.test(value)) {
89
118
  validationError.value = `Contains unsupported value: ${rule}`;
90
119
  return false;
91
120
  }
@@ -125,6 +154,16 @@ function handleBlur(event) {
125
154
  runValidation(target.value);
126
155
  }
127
156
  const isHex = (val) => val.startsWith("#");
157
+ function supportsWidthValue(value) {
158
+ if (typeof CSS !== "undefined" && typeof CSS.supports === "function") {
159
+ return CSS.supports("width", value);
160
+ }
161
+ return /^-?\d*\.?\d+(px|%|rem|em|vh|vw|vmin|vmax|ch|ex|cm|mm|in|pt|pc)$/.test(
162
+ value.trim()
163
+ ) || /^(calc|var|min|max|clamp)\(/.test(value.trim());
164
+ }
165
+ const hasError = computed(() => Boolean(props.error) || Boolean(validationError.value));
166
+ const errorText = computed(() => props.errorMessage || validationError.value);
128
167
  const roundedMap = {
129
168
  none: "rounded-none",
130
169
  sm: "rounded-sm",
@@ -163,12 +202,12 @@ const computedClass = computed(() => {
163
202
  } else if (props.case === "capitalize") {
164
203
  classes += " capitalize";
165
204
  }
166
- if (validationError.value) {
167
- classes += " border-red-500";
205
+ if (hasError.value) {
206
+ classes += " border-red-500 focus:ring-0 ";
168
207
  } else {
169
- classes += ` ${props.color && !isHex(props.color) ? `border-${props.color} focus:ring-${props.color} ` : ""}`;
208
+ classes += ` ${props.color && !isHex(props.color) ? `border-${props.color} focus:ring-0 ` : ""}`;
170
209
  }
171
- if (props.width && !CSS.supports("width", props.width)) {
210
+ if (props.width && !supportsWidthValue(props.width)) {
172
211
  classes += `w-${props.width} `;
173
212
  }
174
213
  if (!props.width) classes += "w-full ";
@@ -178,7 +217,7 @@ const computedClass = computed(() => {
178
217
  });
179
218
  const computedStyle = computed(() => {
180
219
  let styles = {};
181
- if (props.width && CSS.supports("width", props.width)) {
220
+ if (props.width && supportsWidthValue(props.width)) {
182
221
  styles["width"] = props.width;
183
222
  }
184
223
  if (props.color && isHex(props.color)) {
@@ -210,7 +249,7 @@ const inputAttrs = computed(() => {
210
249
  :style="computedStyle"
211
250
  @input="handleInput"
212
251
  @blur="handleBlur"
213
- v-bind="inputAttrs",
252
+ v-bind="inputAttrs"
214
253
  />
215
254
 
216
255
  <label class="label" :class="color && !isHex(color) ? `text-${color}` : ''" :style="color ? { color: isHex(color) ? color : '' } : { color: '#9ca3af' }">
@@ -221,21 +260,21 @@ const inputAttrs = computed(() => {
221
260
  <UIButton v-if="type === 'password'" :icon="visibility ? 'visibility_off' : 'visibility'" flat dense color="gray-400" @click="visibility = !visibility" />
222
261
  </div>
223
262
  <p
224
- v-if="hint && !validationError"
263
+ v-if="hint && !hasError"
225
264
  class="hint-text"
226
265
  >
227
266
  {{ hint }}
228
267
  </p>
229
268
 
230
269
  <p
231
- v-if="validationError"
270
+ v-if="hasError"
232
271
  class="error-text"
233
272
  >
234
- {{ validationError }}
273
+ {{ errorText }}
235
274
  </p>
236
275
  </div>
237
276
  </template>
238
277
 
239
278
  <style scoped>
240
- .input-container{margin:20px 0 10px;position:relative}.input-container.dense{margin:10px 0}.input-container .input{padding:10px}.input-container.dense .input{padding:6px 10px}.input:focus{outline:none}.label{left:10px;padding:0 5px;pointer-events:none;position:absolute;top:50%;transform:translateY(-50%);transition:top .2s ease,font-size .2s ease,color .2s ease,transform .2s ease}.input-container:has(input[type=date]) .label,.input-container:has(input[type=datetime-local]) .label,.input-container:has(input[type=month]) .label,.input-container:has(input[type=time]) .label,.input-container:has(input[type=week]) .label{left:5px;top:-20%;transform:none;z-index:1}.input-container.input-lg.dense .input:-moz-placeholder+.label,.input:-moz-placeholder+.label{left:5px;top:-20px;transform:none;z-index:1}.input-container.input-lg.dense .input.has-value+.label,.input-container.input-lg.dense .input:focus+.label,.input-container.input-lg.dense .input:placeholder-shown+.label,.input.has-value+.label,.input:focus+.label,.input:placeholder-shown+.label{left:5px;top:-20px;transform:none;z-index:1}.input-container.dense .input:-moz-placeholder+.label{top:-10px}.input-container.dense .input.has-value+.label,.input-container.dense .input:focus+.label,.input-container.dense .input:placeholder-shown+.label{top:-10px}.input-container.input-lg .input:-moz-placeholder+.label{top:-24px}.input-container.input-lg .input.has-value+.label,.input-container.input-lg .input:focus+.label,.input-container.input-lg .input:placeholder-shown+.label{top:-24px}.input-container.input-sm .label{font-size:14px}.input-container.input-md .label{font-size:16px}.input-container.input-lg .label{font-size:18px}.input-container.input-sm .input:-moz-placeholder+.label{font-size:12px}.input-container.input-sm .input.has-value+.label,.input-container.input-sm .input:focus+.label,.input-container.input-sm .input:placeholder-shown+.label{font-size:12px}.input-container.input-md .input:-moz-placeholder+.label{font-size:14px}.input-container.input-md .input.has-value+.label,.input-container.input-md .input:focus+.label,.input-container.input-md .input:placeholder-shown+.label{font-size:14px}.input-container.input-lg .input:-moz-placeholder+.label{font-size:16px}.input-container.input-lg .input.has-value+.label,.input-container.input-lg .input:focus+.label,.input-container.input-lg .input:placeholder-shown+.label{font-size:16px}.hint-text{color:#6b7280;font-size:12px;margin-left:5px}.char-count{color:#6b7280}.char-count,.error-text{font-size:.875rem;margin-top:4px}.error-text{color:#ef4444}
279
+ .input-container{box-sizing:border-box;margin:20px 0 10px;min-width:0;position:relative;width:100%}.input-container.dense{margin:10px 0}.input-container .input{padding:10px}.input-container.dense .input{padding:6px 10px}.input:focus{outline:none}.label{left:10px;padding:0 5px;pointer-events:none;position:absolute;top:50%;transform:translateY(-50%);transition:top .2s ease,font-size .2s ease,color .2s ease,transform .2s ease}.input-container:has(input[type=date]) .label,.input-container:has(input[type=datetime-local]) .label,.input-container:has(input[type=month]) .label,.input-container:has(input[type=time]) .label,.input-container:has(input[type=week]) .label{left:5px;top:-20%;transform:none;z-index:1}.input-container.input-lg.dense .input:-moz-placeholder+.label,.input:-moz-placeholder+.label{left:5px;top:-20px;transform:none;z-index:1}.input-container.input-lg.dense .input.has-value+.label,.input-container.input-lg.dense .input:focus+.label,.input-container.input-lg.dense .input:placeholder-shown+.label,.input.has-value+.label,.input:focus+.label,.input:placeholder-shown+.label{left:5px;top:-20px;transform:none;z-index:1}.input-container.dense .input:-moz-placeholder+.label{top:-10px}.input-container.dense .input.has-value+.label,.input-container.dense .input:focus+.label,.input-container.dense .input:placeholder-shown+.label{top:-10px}.input-container.input-lg .input:-moz-placeholder+.label{top:-24px}.input-container.input-lg .input.has-value+.label,.input-container.input-lg .input:focus+.label,.input-container.input-lg .input:placeholder-shown+.label{top:-24px}.input-container.input-sm .label{font-size:14px}.input-container.input-md .label{font-size:16px}.input-container.input-lg .label{font-size:18px}.input-container.input-sm .input:-moz-placeholder+.label{font-size:12px}.input-container.input-sm .input.has-value+.label,.input-container.input-sm .input:focus+.label,.input-container.input-sm .input:placeholder-shown+.label{font-size:12px}.input-container.input-md .input:-moz-placeholder+.label{font-size:14px}.input-container.input-md .input.has-value+.label,.input-container.input-md .input:focus+.label,.input-container.input-md .input:placeholder-shown+.label{font-size:14px}.input-container.input-lg .input:-moz-placeholder+.label{font-size:16px}.input-container.input-lg .input.has-value+.label,.input-container.input-lg .input:focus+.label,.input-container.input-lg .input:placeholder-shown+.label{font-size:16px}.hint-text{color:#6b7280;font-size:12px;margin-left:5px;overflow-wrap:anywhere;word-break:break-word}.char-count{color:#6b7280}.char-count,.error-text{font-size:.875rem;margin-top:4px}.error-text{color:#ef4444;overflow-wrap:anywhere;word-break:break-word}
241
280
  </style>
@@ -28,6 +28,8 @@ type __VLS_Props = {
28
28
  readonly?: boolean;
29
29
  autocomplete?: string;
30
30
  validateOn?: ValidateOn;
31
+ error?: boolean;
32
+ errorMessage?: string;
31
33
  };
32
34
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
33
35
  "update:modelValue": (value: string) => any;
@@ -49,10 +51,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
49
51
  width: string;
50
52
  rounded: string;
51
53
  placeholder: string;
52
- readonly: boolean;
53
54
  hint: string;
55
+ readonly: boolean;
56
+ error: boolean;
54
57
  outlined: boolean;
55
58
  borderless: boolean;
59
+ errorMessage: string;
56
60
  case: "upper" | "lower" | "capitalize" | "normal" | null;
57
61
  allow: ValidationRule[];
58
62
  deny: ValidationRule[];