edvoyui-component-library-test-flight 0.0.254 → 0.0.256

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.
@@ -8,7 +8,18 @@ type AccordionMenuSection = {
8
8
  label: string;
9
9
  items: AccordionMenuItem[];
10
10
  };
11
- declare const _default: import('../../../node_modules/vue').DefineComponent<import('../../../node_modules/vue').ExtractPropTypes<{
11
+ declare function __VLS_template(): {
12
+ attrs: Partial<{}>;
13
+ slots: {
14
+ 'section-label'?(_: {
15
+ section: AccordionMenuSection;
16
+ }): any;
17
+ };
18
+ refs: {};
19
+ rootEl: HTMLElement;
20
+ };
21
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
22
+ declare const __VLS_component: import('../../../node_modules/vue').DefineComponent<import('../../../node_modules/vue').ExtractPropTypes<{
12
23
  items: {
13
24
  type: PropType<AccordionMenuSection[]>;
14
25
  default: () => never[];
@@ -67,4 +78,10 @@ declare const _default: import('../../../node_modules/vue').DefineComponent<impo
67
78
  openSection: string;
68
79
  stickySectionHeaders: boolean;
69
80
  }, {}, {}, {}, string, import('../../../node_modules/vue').ComponentProvideOptions, true, {}, HTMLElement>;
81
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
70
82
  export default _default;
83
+ type __VLS_WithTemplateSlots<T, S> = T & {
84
+ new (): {
85
+ $slots: S;
86
+ };
87
+ };
@@ -17,7 +17,9 @@
17
17
  :aria-expanded="expandedSection === section.id"
18
18
  @click="toggleSection(section.id)"
19
19
  >
20
- <span class="flex-1 min-w-0 truncate">{{ section.label }}</span>
20
+ <slot name="section-label" :section="section">
21
+ <span class="flex-1 min-w-0 truncate">{{ section.label }}</span>
22
+ </slot>
21
23
  <svg
22
24
  class="size-5 shrink-0 transition-transform duration-350 ease-[cubic-bezier(0.15,0.3,0.25,1)]"
23
25
  :class="expandedSection === section.id ? 'rotate-90' : ''"
@@ -1,40 +1,23 @@
1
- import { PropType } from '../../../node_modules/vue';
2
- declare const _default: import('../../../node_modules/vue').DefineComponent<import('../../../node_modules/vue').ExtractPropTypes<{
3
- modelValue: {
4
- type: (StringConstructor | NumberConstructor)[];
5
- default: string;
6
- };
7
- searchSync: {
8
- type: BooleanConstructor;
9
- default: boolean;
10
- };
11
- type: {
12
- type: PropType<"text" | "number" | "email" | "password" | "search">;
13
- default: string;
14
- };
15
- }>, {}, {}, {}, {}, import('../../../node_modules/vue').ComponentOptionsMixin, import('../../../node_modules/vue').ComponentOptionsMixin, {
16
- "update:modelValue": (...args: any[]) => void;
17
- }, string, import('../../../node_modules/vue').PublicProps, Readonly<import('../../../node_modules/vue').ExtractPropTypes<{
18
- modelValue: {
19
- type: (StringConstructor | NumberConstructor)[];
20
- default: string;
21
- };
22
- searchSync: {
23
- type: BooleanConstructor;
24
- default: boolean;
25
- };
26
- type: {
27
- type: PropType<"text" | "number" | "email" | "password" | "search">;
28
- default: string;
29
- };
30
- }>> & Readonly<{
31
- "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
1
+ type InputType = "text" | "number" | "email" | "password" | "search";
2
+ interface Props {
3
+ autoFocus?: boolean;
4
+ modelValue?: string | number;
5
+ defaultExpand?: boolean;
6
+ searchSync?: boolean;
7
+ type?: InputType;
8
+ }
9
+ declare const _default: import('../../../node_modules/vue').DefineComponent<Props, {}, {}, {}, {}, import('../../../node_modules/vue').ComponentOptionsMixin, import('../../../node_modules/vue').ComponentOptionsMixin, {
10
+ "update:modelValue": (value: string | number) => any;
11
+ }, string, import('../../../node_modules/vue').PublicProps, Readonly<Props> & Readonly<{
12
+ "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
32
13
  }>, {
33
- type: "number" | "text" | "search" | "email" | "password";
14
+ type: InputType;
34
15
  modelValue: string | number;
16
+ autoFocus: boolean;
17
+ defaultExpand: boolean;
35
18
  searchSync: boolean;
36
- }, {}, {}, {}, string, import('../../../node_modules/vue').ComponentProvideOptions, true, {
19
+ }, {}, {}, {}, string, import('../../../node_modules/vue').ComponentProvideOptions, false, {
37
20
  componentWrapper: HTMLDivElement;
38
- input: HTMLInputElement;
21
+ inputRef: HTMLInputElement;
39
22
  }, HTMLDivElement>;
40
23
  export default _default;
@@ -1,128 +1,146 @@
1
1
  <template>
2
2
  <div class="relative rounded-full size-10">
3
- <div class="absolute top-0 right-0">
3
+ <div ref="componentWrapper" class="absolute top-0 right-0 z-50">
4
4
  <div
5
- ref="componentWrapper"
6
- class="cursor-pointer group"
7
- @click="expandInput"
5
+ :class="[
6
+ 'relative flex h-10 items-center rounded-full bg-gray-100 ring-offset-0',
7
+ 'transition-all duration-300 ease-in-out',
8
+ 'border border-gray-200 focus-within:border-gray-100',
9
+ 'focus-within:ring-2 focus-within:ring-purple-500',
10
+ isExpanded
11
+ ? 'w-72 group-focus-within:w-72 text-gray-400'
12
+ : 'w-10 text-gray-500',
13
+ ]"
14
+ @click.stop="expandInput"
8
15
  >
9
- <div class="relative max-w-80 z-[calc(infinity)]">
10
- <div
11
- :class="[
12
- 'relative flex items-center duration-300 ease-linear bg-gray-100 rounded-full pe-1 group-focus-within:ring-2 ring-purple-200 group-focus-within:border-gray-200 group-focus-within:border transition-all delay-75',
13
- isExpanded
14
- ? 'group-focus-within:w-80 text-gray-300 h-10'
15
- : 'size-10 text-gray-500',
16
- ]"
16
+ <SearchBigZoomIn
17
+ class="absolute text-current pointer-events-none left-2 top-2 size-6"
18
+ />
19
+
20
+ <!-- Expand trigger -->
21
+ <button
22
+ v-if="!isExpanded"
23
+ type="button"
24
+ class="absolute inset-0 rounded-full cursor-pointer"
25
+ aria-label="Open search"
26
+ @click="expandInput"
27
+ />
28
+
29
+ <!-- Search input -->
30
+ <input
31
+ v-else
32
+ ref="inputRef"
33
+ v-model="searchQuery"
34
+ :type="inputType"
35
+ placeholder="Search here..."
36
+ autocomplete="off"
37
+ class="w-full h-10 text-sm font-medium text-gray-900 bg-transparent outline-none ps-10 pe-10"
38
+ />
39
+
40
+ <!-- Clear -->
41
+ <button
42
+ v-if="isExpanded && hasSearchValue"
43
+ type="button"
44
+ class="absolute inset-y-0 z-20 flex items-center justify-center text-gray-500 transition-colors pointer-events-auto right-2 hover:text-red-500"
45
+ @mousedown.prevent
46
+ @click="clearSearch"
47
+ >
48
+ <span
49
+ class="flex items-center justify-center p-1 bg-white rounded-full hover:bg-red-50 size-6"
17
50
  >
18
- <SearchBigZoomIn
19
- class="absolute text-current size-6 top-2 left-2"
20
- />
21
-
22
- <button
23
- v-if="isExpanded"
24
- type="button"
25
- aria-label="Clear search"
26
- :class="
27
- inputValue ? ' absolute inset-y-0 right-3 ' : ' hidden '
28
- "
29
- @mousedown.prevent
30
- @click.stop="clearSearch"
31
- >
32
- <svg
33
- xmlns="http://www.w3.org/2000/svg"
34
- fill="none"
35
- viewBox="0 0 24 24"
36
- stroke-width="1.5"
37
- stroke="currentColor"
38
- class="size-5"
39
- >
40
- <!-- <path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
41
- -->
42
- <path
43
- stroke-linecap="round"
44
- stroke-linejoin="round"
45
- d="M6 18 18 6M6 6l12 12"
46
- />
47
- </svg>
48
- </button>
49
-
50
- <input
51
- v-if="isExpanded"
52
- v-model="searchQuery"
53
- ref="input"
54
- :type="type === 'search' ? 'text' : type"
55
- placeholder="Search here..."
56
- class="h-10 text-sm font-medium text-gray-900 bg-transparent w-80 ps-10 pe-2 focus:outline-none"
57
- autocomplete="off"
58
- @input="handleSearch"
59
- @blur="closeInput"
60
- />
61
- </div>
62
- </div>
51
+ <XMarkIcon class="transition-colors size-5" />
52
+ </span>
53
+ </button>
63
54
  </div>
64
55
  </div>
65
56
  </div>
66
57
  </template>
67
58
 
68
- <script lang="ts" setup>
59
+ <script setup lang="ts">
69
60
  import {
70
61
  computed,
71
62
  nextTick,
72
63
  onBeforeUnmount,
73
64
  onMounted,
74
- PropType,
75
65
  ref,
76
- toRefs,
77
66
  watch,
78
67
  } from "vue";
79
68
  import SearchBigZoomIn from "../../assets/svg/SearchBigZoomIn.vue";
69
+ import { XMarkIcon } from "@heroicons/vue/20/solid";
80
70
 
81
- const props = defineProps({
82
- modelValue: {
83
- type: [String, Number],
84
- default: "",
85
- },
86
- searchSync: {
87
- type: Boolean,
88
- default: false,
89
- },
90
- type: {
91
- type: String as PropType<
92
- "text" | "number" | "email" | "password" | "search"
93
- >,
94
- default: "text",
95
- },
71
+ type InputType = "text" | "number" | "email" | "password" | "search";
72
+
73
+ interface Props {
74
+ autoFocus?: boolean;
75
+ modelValue?: string | number;
76
+ defaultExpand?: boolean;
77
+ searchSync?: boolean;
78
+ type?: InputType;
79
+ }
80
+
81
+ const props = withDefaults(defineProps<Props>(), {
82
+ autoFocus: true,
83
+ modelValue: "",
84
+ defaultExpand: false,
85
+ searchSync: false,
86
+ type: "text",
96
87
  });
97
- const { searchSync } = toRefs(props);
98
- const emit = defineEmits(["update:modelValue"]);
99
88
 
100
- const input = ref<HTMLInputElement>();
101
- const isExpanded = ref(true);
102
- const searchQuery = ref<string | number>(props.modelValue ?? "");
89
+ const emit = defineEmits<{
90
+ "update:modelValue": [value: string | number];
91
+ }>();
92
+
93
+ const inputRef = ref<HTMLInputElement | null>(null);
103
94
  const componentWrapper = ref<HTMLElement | null>(null);
104
95
 
96
+ const hasValue = (value: string | number | undefined) =>
97
+ value === 0 || Boolean(value);
98
+
99
+ const isExpanded = ref(
100
+ props.defaultExpand || props.searchSync || hasValue(props.modelValue)
101
+ );
102
+
103
+ /**
104
+ * Direct v-model bridge.
105
+ *
106
+ * Removes the need for:
107
+ * - local searchQuery ref
108
+ * - modelValue watcher
109
+ * - @input handler
110
+ */
111
+ const searchQuery = computed({
112
+ get: () => props.modelValue ?? "",
113
+ set: (value: string | number) => {
114
+ emit("update:modelValue", value);
115
+ },
116
+ });
117
+
118
+ const hasSearchValue = computed(() => hasValue(searchQuery.value));
119
+
120
+ const inputType = computed(() =>
121
+ props.type === "search" ? "text" : props.type
122
+ );
123
+
105
124
  async function expandInput() {
125
+ if (isExpanded.value) return;
126
+
106
127
  isExpanded.value = true;
128
+
107
129
  await nextTick();
108
- input.value?.focus();
130
+ inputRef.value?.focus();
109
131
  }
110
132
 
111
133
  async function clearSearch() {
112
134
  searchQuery.value = "";
113
- emit("update:modelValue", "");
135
+
114
136
  await nextTick();
115
- input.value?.focus();
137
+ inputRef.value?.focus();
116
138
  }
117
139
 
118
- const inputValue = computed(() => {
119
- return props.modelValue === 0 ? true : !!props.modelValue;
120
- });
121
-
122
140
  function closeInput() {
123
- if (!searchQuery.value) {
124
- isExpanded.value = false;
125
- }
141
+ if (props.defaultExpand || hasSearchValue.value) return;
142
+
143
+ isExpanded.value = false;
126
144
  }
127
145
 
128
146
  function handleOutsideClick(event: MouseEvent) {
@@ -135,43 +153,22 @@ function handleOutsideClick(event: MouseEvent) {
135
153
  }
136
154
 
137
155
  watch(
138
- searchSync,
139
- (newVal) => {
140
- if (!newVal) {
156
+ () => props.searchSync,
157
+ (enabled) => {
158
+ if (!enabled) {
141
159
  closeInput();
142
160
  }
143
- },
144
- { immediate: true, deep: true }
145
- );
146
-
147
- watch(
148
- () => props.modelValue,
149
- (newValue) => {
150
- if (newValue !== searchQuery.value) {
151
- searchQuery.value = newValue ?? "";
152
- }
153
161
  }
154
162
  );
155
163
 
156
164
  onMounted(() => {
157
165
  document.addEventListener("click", handleOutsideClick);
158
- if (isExpanded.value) {
159
- nextTick(() => input.value?.focus());
166
+ if (isExpanded.value && props.autoFocus) {
167
+ nextTick(() => inputRef.value?.focus());
160
168
  }
161
169
  });
162
170
 
163
171
  onBeforeUnmount(() => {
164
172
  document.removeEventListener("click", handleOutsideClick);
165
173
  });
166
-
167
- function handleSearch(event: Event) {
168
- const newValue = (event?.target as HTMLInputElement)?.value;
169
- if (newValue !== undefined) {
170
- emit("update:modelValue", newValue);
171
- }
172
- }
173
174
  </script>
174
-
175
- <style scoped>
176
- /* Add custom styles here */
177
- </style>