@irina_grigoreva/accessible-vue-search-select 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,274 @@
1
+ # Accessible Vue Search Select
2
+
3
+ ![Vue](https://img.shields.io/badge/Vue-3.5+-42b883)
4
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
5
+ ![Accessibility](https://img.shields.io/badge/a11y-ARIA%20combobox-green)
6
+ ![Tests](https://img.shields.io/badge/tests-vitest-yellow)
7
+
8
+ A reusable and accessible Vue 3 combobox/listbox component built with TypeScript.
9
+
10
+ ![Accessible Vue Search Select demo](./docs/demo-screenshot.svg)
11
+
12
+ ## Features
13
+
14
+ - Vue 3.5+ component built with `<script setup lang="ts">`
15
+ - Accessible searchable combobox/listbox pattern
16
+ - `v-model` support for objects and primitive values via `emitValue`
17
+ - Teleported dropdown with optional inline listbox mode
18
+ - Loading, empty, disabled, clearable, and error states
19
+ - Light and dark mode support via CSS custom properties
20
+ - Custom option and selected-value slots
21
+ - Flexible `labelKey`, `valueKey`, and `searchKeys` mapping
22
+ - Configurable `inputId`, `listboxId`, and ARIA labels
23
+ - Stable ARIA relationships:
24
+ - `aria-controls`
25
+ - `aria-expanded`
26
+ - `aria-activedescendant`
27
+ - `aria-selected`
28
+ - Keyboard navigation support
29
+ - Vitest + Vue Test Utils coverage
30
+ - Library build with exported TypeScript types
31
+
32
+ ## Demo
33
+
34
+ <video src="./demo/demo.mp4" controls autoplay loop muted playsinline></video>
35
+
36
+ Run the local demo:
37
+
38
+ ```bash
39
+ npm install
40
+ npm run dev
41
+ ```
42
+
43
+ Then open:
44
+
45
+ ```text
46
+ http://127.0.0.1:5173/
47
+ ```
48
+
49
+ The demo source is in [examples/SelectSimpleSearchDemo.vue](./examples/SelectSimpleSearchDemo.vue).
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ npm install accessible-vue-search-select
55
+ ```
56
+
57
+ Import the component and styles:
58
+
59
+ ```ts
60
+ import { SelectSimpleSearch } from "accessible-vue-search-select";
61
+ import "accessible-vue-search-select/dist/accessible-vue-search-select.css";
62
+ ```
63
+
64
+ For local development in this repository:
65
+
66
+ ```bash
67
+ npm install
68
+ npm run typecheck
69
+ npm run test
70
+ npm run build
71
+ ```
72
+
73
+ ## Basic Usage
74
+
75
+ ```vue
76
+ <script setup lang="ts">
77
+ import { ref } from "vue";
78
+ import { SelectSimpleSearch, type SelectOption } from "accessible-vue-search-select";
79
+ import "accessible-vue-search-select/dist/accessible-vue-search-select.css";
80
+
81
+ const selected = ref<SelectOption | null>(null);
82
+
83
+ const states = [
84
+ { label: "California", value: "ca" },
85
+ { label: "New York", value: "ny" },
86
+ { label: "Texas", value: "tx" },
87
+ ];
88
+ </script>
89
+
90
+ <template>
91
+ <SelectSimpleSearch
92
+ v-model="selected"
93
+ label="State"
94
+ placeholder="Choose a state"
95
+ :options="states"
96
+ label-key="label"
97
+ value-key="value"
98
+ />
99
+ </template>
100
+ ```
101
+
102
+ Primitive `v-model` values:
103
+
104
+ ```vue
105
+ <script setup lang="ts">
106
+ import { ref } from "vue";
107
+
108
+ const selectedState = ref<string | number | null>(null);
109
+ </script>
110
+
111
+ <template>
112
+ <SelectSimpleSearch
113
+ v-model="selectedState"
114
+ emit-value
115
+ label="State"
116
+ :options="states"
117
+ />
118
+ </template>
119
+ ```
120
+
121
+ Facility-style objects are supported by default:
122
+
123
+ ```ts
124
+ const facilities = [
125
+ {
126
+ id: 1,
127
+ slug: "seattle-storage",
128
+ public_title: "",
129
+ address: {
130
+ city: "Seattle",
131
+ full_address: "8 Pine Street, Seattle, WA",
132
+ },
133
+ },
134
+ ];
135
+ ```
136
+
137
+ This renders the selected label as `Seattle - Storage Facility`.
138
+
139
+ ## Props
140
+
141
+ | Prop | Type | Default | Description |
142
+ | --- | --- | --- | --- |
143
+ | `options` | `SelectOption[]` | `[]` | Options to render. |
144
+ | `modelValue` | `SelectOption \| string \| number \| null` | `null` | Selected value for `v-model`. |
145
+ | `placeholder` | `string` | `"Select an option"` | Input placeholder. |
146
+ | `type` | `string` | `"default"` | Optional compatibility class. |
147
+ | `inlineListbox` | `boolean` | `false` | Render listbox inline instead of teleporting to `body`. |
148
+ | `closeOnBlur` | `boolean` | `false` | Close after input blur. |
149
+ | `label` | `string` | `""` | Visible input label. |
150
+ | `name` | `string` | `"select-simple-search"` | Input name. |
151
+ | `inputId` | `string` | generated | Input id. |
152
+ | `listboxId` | `string` | generated | Listbox id used by `aria-controls` and option ids. |
153
+ | `ariaLabel` | `string` | `"Search options"` | Accessible label when no visible label is provided. |
154
+ | `labelKey` | `string` | `""` | Dot-path used for option labels. |
155
+ | `valueKey` | `string` | `""` | Dot-path used for option values. |
156
+ | `searchKeys` | `string[]` | common label/facility fields | Dot-path fields searched by the input. |
157
+ | `disabled` | `boolean` | `false` | Disable the component. |
158
+ | `clearable` | `boolean` | `true` | Show clear button when selected. |
159
+ | `clearLabel` | `string` | `"Clear selection"` | Clear button accessible label. |
160
+ | `noResultsText` | `string` | `"No results found"` | Empty state text. |
161
+ | `loading` | `boolean` | `false` | Show loading state. |
162
+ | `loadingText` | `string` | `"Loading options..."` | Loading state text. |
163
+ | `maxHeight` | `string` | `"18rem"` | Dropdown max height. |
164
+ | `dropdownClass` | `string` | `""` | Extra dropdown class. |
165
+ | `optionClass` | `string` | `""` | Extra option class. |
166
+ | `hasError` | `boolean` | `false` | Apply error styling and `aria-invalid`. |
167
+ | `facilityFallbackLabel` | `string` | `"Storage Facility"` | Fallback title for legacy facility options without `public_title`. |
168
+ | `emitValue` | `boolean` | `false` | Emit option value instead of the whole option object. |
169
+ | `sortOptions` | `boolean` | `false` | Sort filtered options alphabetically by display label. |
170
+
171
+ ## Emits
172
+
173
+ | Event | Payload | Description |
174
+ | --- | --- | --- |
175
+ | `update:modelValue` | `SelectOption \| string \| number \| null` | Emitted for `v-model`. |
176
+ | `change` | `SelectOption \| string \| number \| null` | Emitted when selection changes. |
177
+ | `blur` | none | Emitted after input blur handling. |
178
+ | `clear` | none | Emitted when selection is cleared. |
179
+ | `open` | none | Emitted when listbox opens. |
180
+ | `close` | none | Emitted when listbox closes. |
181
+
182
+ ## Slots
183
+
184
+ | Slot | Props | Description |
185
+ | --- | --- | --- |
186
+ | `option` | `{ option, selected, active }` | Custom option rendering. |
187
+ | `selected` | `{ option }` | Custom selected-value rendering. |
188
+ | `no-results` | none | Custom empty state. |
189
+ | `loading` | none | Custom loading state. |
190
+
191
+ Example:
192
+
193
+ ```vue
194
+ <SelectSimpleSearch v-model="facility" :options="facilities">
195
+ <template #option="{ option, active, selected }">
196
+ <div :class="{ active, selected }">
197
+ <strong>{{ option.address?.city }}</strong>
198
+ <span>{{ option.public_title || "Storage Facility" }}</span>
199
+ <small>{{ option.address?.full_address }}</small>
200
+ </div>
201
+ </template>
202
+ </SelectSimpleSearch>
203
+ ```
204
+
205
+ ## Accessibility
206
+
207
+ The component keeps DOM focus on the input and uses `aria-activedescendant` for active-option tracking. The input exposes `role="combobox"`, `aria-expanded`, `aria-controls`, `aria-autocomplete="list"`, and `aria-haspopup="listbox"`.
208
+
209
+ When open, the listbox exists in the DOM with `role="listbox"`. Each option has `role="option"`, a stable `id`, and `aria-selected="true"` or `aria-selected="false"`.
210
+
211
+ For best screen reader support, pass either a visible `label` or an `ariaLabel`.
212
+
213
+ ## Dark Mode
214
+
215
+ The component supports dark mode automatically through `prefers-color-scheme`. You can also force a theme by setting `data-theme` on any parent:
216
+
217
+ ```html
218
+ <div data-theme="dark">
219
+ <SelectSimpleSearch />
220
+ </div>
221
+ ```
222
+
223
+ Theme colors are exposed as CSS custom properties on `.select-simple`, so design systems can override them:
224
+
225
+ ```css
226
+ .select-simple {
227
+ --select-simple-bg: #ffffff;
228
+ --select-simple-text: #111827;
229
+ --select-simple-border: #9ca3af;
230
+ --select-simple-option-active-bg: #eff6ff;
231
+ --select-simple-option-selected-bg: #dbeafe;
232
+ }
233
+ ```
234
+
235
+ ## Keyboard Support
236
+
237
+ | Key | Behavior |
238
+ | --- | --- |
239
+ | `ArrowDown` | Opens the listbox or moves to the next enabled option. |
240
+ | `ArrowUp` | Opens the listbox or moves to the previous enabled option. |
241
+ | `Enter` | Selects the active option while the listbox is open. |
242
+ | `Escape` | Closes the listbox and returns focus to the input. |
243
+ | `Backspace` / `Delete` | Clears the selected value when the selected label is shown. |
244
+ | `Home` / `End` | Preserve native text-input caret behavior. |
245
+
246
+ ## License
247
+
248
+ MIT
249
+
250
+ ## Publishing
251
+
252
+ Do not publish before running the full package verification:
253
+
254
+ ```bash
255
+ npm run lint
256
+ npm run typecheck
257
+ npm run test
258
+ npm run build
259
+ npm pack --dry-run
260
+ ```
261
+
262
+ Publish manually when the package contents look correct:
263
+
264
+ ```bash
265
+ npm login
266
+ npm publish --access public
267
+ ```
268
+
269
+ The package is configured to publish only:
270
+
271
+ - `dist`
272
+ - `README.md`
273
+ - `LICENSE`
274
+ - `package.json`
@@ -0,0 +1 @@
1
+ .select-simple[data-v-58b7f39e],.select-simple-options[data-v-58b7f39e]{--select-simple-bg: #fff;--select-simple-text: #111827;--select-simple-muted: #4b5563;--select-simple-border: #9ca3af;--select-simple-border-subtle: #d1d5db;--select-simple-focus-border: #2563eb;--select-simple-focus-ring: #bfdbfe;--select-simple-clear-hover-bg: #f3f4f6;--select-simple-option-active-bg: #eff6ff;--select-simple-option-selected-bg: #dbeafe;--select-simple-option-hover-bg: #f8fafc;--select-simple-error: #dc2626;--select-simple-shadow: 0 10px 24px rgb(15 23 42 / 14%);position:relative;width:100%;color:var(--select-simple-text)}@media(prefers-color-scheme:dark){.select-simple[data-v-58b7f39e],.select-simple-options[data-v-58b7f39e]{--select-simple-bg: #111827;--select-simple-text: #f9fafb;--select-simple-muted: #cbd5e1;--select-simple-border: #475569;--select-simple-border-subtle: #334155;--select-simple-focus-border: #60a5fa;--select-simple-focus-ring: #1e3a8a;--select-simple-clear-hover-bg: #1f2937;--select-simple-option-active-bg: #1d4ed8;--select-simple-option-selected-bg: #1e40af;--select-simple-option-hover-bg: #1f2937;--select-simple-error: #f87171;--select-simple-shadow: 0 14px 32px rgb(0 0 0 / 34%)}}[data-theme=light]{--select-simple-bg: #fff;--select-simple-text: #111827;--select-simple-muted: #4b5563;--select-simple-border: #9ca3af;--select-simple-border-subtle: #d1d5db;--select-simple-focus-border: #2563eb;--select-simple-focus-ring: #bfdbfe;--select-simple-clear-hover-bg: #f3f4f6;--select-simple-option-active-bg: #eff6ff;--select-simple-option-selected-bg: #dbeafe;--select-simple-option-hover-bg: #f8fafc;--select-simple-error: #dc2626;--select-simple-shadow: 0 10px 24px rgb(15 23 42 / 14%)}[data-theme=dark]{--select-simple-bg: #111827;--select-simple-text: #f9fafb;--select-simple-muted: #cbd5e1;--select-simple-border: #475569;--select-simple-border-subtle: #334155;--select-simple-focus-border: #60a5fa;--select-simple-focus-ring: #1e3a8a;--select-simple-clear-hover-bg: #1f2937;--select-simple-option-active-bg: #1d4ed8;--select-simple-option-selected-bg: #1e40af;--select-simple-option-hover-bg: #1f2937;--select-simple-error: #f87171;--select-simple-shadow: 0 14px 32px rgb(0 0 0 / 34%)}.select-simple-label[data-v-58b7f39e]{display:block;margin-bottom:.375rem;font-weight:600}.select-simple-control[data-v-58b7f39e]{position:relative}.select-simple.has-selected-slot .select-simple-control[data-v-58b7f39e]>:not(input,button){position:absolute;top:50%;left:.75rem;z-index:1;max-width:calc(100% - 3rem);overflow:hidden;pointer-events:none;text-overflow:ellipsis;white-space:nowrap;transform:translateY(-50%)}.select-simple.has-selected-slot:focus-within .select-simple-control[data-v-58b7f39e]>:not(input,button){display:none}.select-simple-input[data-v-58b7f39e]{box-sizing:border-box;width:100%;min-height:2.5rem;padding:.5rem 2.25rem .5rem .75rem;border:1px solid var(--select-simple-border);border-radius:6px;background-color:var(--select-simple-bg);color:var(--select-simple-text);font:inherit}.select-simple.has-selected-slot .select-simple-input[data-v-58b7f39e]{color:transparent}.select-simple.has-selected-slot .select-simple-input[data-v-58b7f39e]:focus{color:var(--select-simple-text)}.select-simple-input[data-v-58b7f39e]:focus{border-color:var(--select-simple-focus-border);outline:2px solid var(--select-simple-focus-ring);outline-offset:1px}.select-simple.disabled[data-v-58b7f39e]{opacity:.65}.select-simple.has-error .select-simple-input[data-v-58b7f39e]{border-color:var(--select-simple-error)}.select-simple-clear[data-v-58b7f39e]{position:absolute;top:50%;right:.5rem;display:inline-flex;width:1.75rem;height:1.75rem;align-items:center;justify-content:center;padding:0;border:0;border-radius:4px;background:transparent;color:var(--select-simple-muted);cursor:pointer;transform:translateY(-50%)}.select-simple-clear[data-v-58b7f39e]:hover{background:var(--select-simple-clear-hover-bg);color:var(--select-simple-text)}.select-simple-overlay[data-v-58b7f39e]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:999;pointer-events:none}.select-simple-options[data-v-58b7f39e]{position:fixed;z-index:1000;box-sizing:border-box;overflow-y:auto;border:1px solid var(--select-simple-border-subtle);border-radius:6px;background-color:var(--select-simple-bg);box-shadow:var(--select-simple-shadow);color:var(--select-simple-text)}[data-v-58b7f39e]::-webkit-scrollbar{width:6px}[data-v-58b7f39e]::-webkit-scrollbar-thumb{background:#0003;border-radius:999px}.select-simple-options-inline[data-v-58b7f39e]{position:relative;z-index:1;width:100%;margin-top:.25rem;box-shadow:none}.select-simple-option[data-v-58b7f39e],.select-simple-status[data-v-58b7f39e]{padding:.625rem .75rem}.select-simple-option[data-v-58b7f39e]{background-color:var(--select-simple-bg);cursor:pointer}.select-simple-option[data-v-58b7f39e]:hover{background-color:var(--select-simple-option-hover-bg)}.select-simple-option.active[data-v-58b7f39e]{background-color:var(--select-simple-option-active-bg)}.select-simple-option.selected[data-v-58b7f39e]{background-color:var(--select-simple-option-selected-bg)}.select-simple-option.disabled[data-v-58b7f39e]{color:var(--select-simple-muted);cursor:not-allowed}.select-simple-option-text[data-v-58b7f39e]{display:grid;gap:.125rem}.select-simple-option-description[data-v-58b7f39e]{color:var(--select-simple-muted);font-size:.875rem}.select-simple-status[data-v-58b7f39e]{color:var(--select-simple-muted)}.sr-only[data-v-58b7f39e]{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@@ -0,0 +1,468 @@
1
+ import { defineComponent as Oe, useId as De, computed as m, ref as f, toRefs as Re, watch as fe, onMounted as Te, onUnmounted as Ee, openBlock as c, createElementBlock as v, normalizeClass as J, unref as o, toDisplayString as _, createCommentVNode as x, createElementVNode as h, renderSlot as M, withDirectives as Ke, vModelText as Me, withModifiers as ve, createBlock as ze, Teleport as Ae, normalizeStyle as Fe, createTextVNode as pe, Fragment as Ne, renderList as je, nextTick as z } from "vue";
2
+ const He = ["for"], Pe = ["id", "name", "disabled", "placeholder", "aria-label", "aria-expanded", "aria-controls", "aria-activedescendant", "aria-invalid", "aria-busy"], Ue = ["aria-label"], qe = {
3
+ class: "sr-only",
4
+ "aria-live": "polite",
5
+ "aria-atomic": "true"
6
+ }, We = {
7
+ key: 0,
8
+ class: "select-simple-overlay"
9
+ }, Ze = ["id"], Ge = {
10
+ key: 0,
11
+ class: "select-simple-status"
12
+ }, Je = ["id", "aria-selected", "aria-disabled", "onClick"], Qe = { class: "select-simple-option-text" }, Xe = {
13
+ key: 0,
14
+ class: "select-simple-option-description"
15
+ }, Ye = {
16
+ key: 2,
17
+ class: "select-simple-status"
18
+ }, el = /* @__PURE__ */ Oe({
19
+ __name: "SelectSimpleSearch",
20
+ props: {
21
+ options: { default: () => [] },
22
+ modelValue: { default: null },
23
+ placeholder: { default: "Select an option" },
24
+ type: { default: "default" },
25
+ inlineListbox: { type: Boolean, default: !1 },
26
+ closeOnBlur: { type: Boolean, default: !1 },
27
+ label: { default: "" },
28
+ name: { default: "select-simple-search" },
29
+ inputId: { default: "" },
30
+ listboxId: { default: "" },
31
+ ariaLabel: { default: "Search options" },
32
+ labelKey: { default: "" },
33
+ valueKey: { default: "" },
34
+ searchKeys: { default: () => [
35
+ "label",
36
+ "name",
37
+ "public_title",
38
+ "address.city",
39
+ "address.full_address",
40
+ "slug",
41
+ "value",
42
+ "id"
43
+ ] },
44
+ disabled: { type: Boolean, default: !1 },
45
+ clearable: { type: Boolean, default: !0 },
46
+ clearLabel: { default: "Clear selection" },
47
+ noResultsText: { default: "No results found" },
48
+ loading: { type: Boolean, default: !1 },
49
+ loadingText: { default: "Loading options..." },
50
+ maxHeight: { default: "18rem" },
51
+ dropdownClass: { default: "" },
52
+ optionClass: { default: "" },
53
+ hasError: { type: Boolean, default: !1 },
54
+ facilityFallbackLabel: { default: "Storage Facility" },
55
+ emitValue: { type: Boolean, default: !1 },
56
+ sortOptions: { type: Boolean, default: !1 }
57
+ },
58
+ emits: ["update:modelValue", "change", "blur", "clear", "open", "close"],
59
+ setup(V, { emit: A }) {
60
+ const a = V, d = A, S = De().replace(/[^A-Za-z0-9_-]/g, "-"), F = m(() => a.listboxId || `select-simple-listbox-${S}`), Q = m(() => a.inputId || `select-simple-input-${S}`), X = f(null), N = f(null), $ = f(null), B = f(null), s = f(!1), p = f(""), i = f(-1), j = f(""), Y = f({}), I = f(!1), {
61
+ ariaLabel: be,
62
+ clearLabel: me,
63
+ clearable: ge,
64
+ disabled: H,
65
+ dropdownClass: ye,
66
+ hasError: ee,
67
+ inlineListbox: P,
68
+ label: U,
69
+ loading: le,
70
+ loadingText: he,
71
+ name: we,
72
+ noResultsText: ke,
73
+ optionClass: _e,
74
+ placeholder: xe,
75
+ type: te
76
+ } = Re(a), Le = m(() => {
77
+ const e = { maxHeight: a.maxHeight };
78
+ return a.inlineListbox ? e : { ...e, ...Y.value };
79
+ }), L = m(() => {
80
+ const e = a.modelValue;
81
+ if (e == null)
82
+ return null;
83
+ if (oe(e)) {
84
+ const l = b(e);
85
+ return a.options.find((t) => D(b(t), l)) ?? e;
86
+ }
87
+ return a.options.find((l) => D(b(l), e)) ?? null;
88
+ }), Ce = m(() => L.value ? g(L.value) : ""), O = m(() => a.modelValue !== null && a.modelValue !== void 0), ae = m({
89
+ get() {
90
+ return p.value || Ce.value;
91
+ },
92
+ set(e) {
93
+ p.value = e;
94
+ }
95
+ }), u = m(() => {
96
+ const e = p.value.trim().toLowerCase(), l = e ? a.options.filter(
97
+ (t) => a.searchKeys.some((n) => W(q(t, n)).toLowerCase().includes(e))
98
+ ) : a.options;
99
+ return a.sortOptions ? [...l].sort((t, n) => {
100
+ const r = g(t).toLowerCase(), k = g(n).toLowerCase();
101
+ return r.localeCompare(k);
102
+ }) : [...l];
103
+ }), ne = m(() => {
104
+ if (!(a.loading || !s.value || i.value < 0 || i.value >= u.value.length))
105
+ return se(i.value);
106
+ });
107
+ function oe(e) {
108
+ return typeof e == "object" && e !== null;
109
+ }
110
+ function q(e, l) {
111
+ return l.split(".").reduce((t, n) => {
112
+ if (t && typeof t == "object" && n in t)
113
+ return t[n];
114
+ }, e);
115
+ }
116
+ function W(e) {
117
+ return e == null ? "" : String(e);
118
+ }
119
+ function b(e) {
120
+ if (a.valueKey) {
121
+ const l = q(e, a.valueKey);
122
+ if (typeof l == "string" || typeof l == "number")
123
+ return l;
124
+ }
125
+ return e.slug ?? e.value ?? e.id;
126
+ }
127
+ function g(e) {
128
+ var l, t, n;
129
+ if (a.labelKey) {
130
+ const r = W(q(e, a.labelKey)).trim();
131
+ if (r)
132
+ return r;
133
+ }
134
+ if (e.label)
135
+ return e.label;
136
+ if (e.name)
137
+ return e.name;
138
+ if ((l = e.address) != null && l.city || e.public_title) {
139
+ const r = (n = (t = e.address) == null ? void 0 : t.city) == null ? void 0 : n.trim(), k = e.public_title && e.public_title.length > 1 ? e.public_title : a.facilityFallbackLabel;
140
+ return [r, k].filter(Boolean).join(" - ");
141
+ }
142
+ return W(b(e));
143
+ }
144
+ function ie(e) {
145
+ var l;
146
+ return ((l = e.address) == null ? void 0 : l.full_address) ?? "";
147
+ }
148
+ function Ve(e, l) {
149
+ const t = b(e);
150
+ return t !== void 0 ? String(t) : `${g(e)}-${l}`;
151
+ }
152
+ function se(e) {
153
+ return `${F.value}-option-${e}`;
154
+ }
155
+ function D(e, l) {
156
+ return e === void 0 || l === void 0 ? !1 : String(e) === String(l);
157
+ }
158
+ function Z(e) {
159
+ const l = a.modelValue;
160
+ if (l == null)
161
+ return !1;
162
+ if (oe(l)) {
163
+ const t = b(l), n = b(e);
164
+ return D(t, n) || l === e;
165
+ }
166
+ return D(b(e), l);
167
+ }
168
+ function y(e) {
169
+ j.value = "", z(() => {
170
+ j.value = e;
171
+ });
172
+ }
173
+ function G(e) {
174
+ return `${e} ${e === 1 ? "option" : "options"} available.`;
175
+ }
176
+ function R() {
177
+ if (a.inlineListbox || !N.value)
178
+ return;
179
+ const e = N.value.getBoundingClientRect();
180
+ Y.value = {
181
+ left: `${e.left}px`,
182
+ top: `${e.bottom + 4}px`,
183
+ width: `${e.width}px`
184
+ };
185
+ }
186
+ async function w() {
187
+ a.disabled || s.value || (I.value = !0, R(), s.value = !0, d("open"), await z(), R(), C(a.loading ? -1 : E()), y(a.loading ? a.loadingText : G(u.value.length)), window.setTimeout(() => {
188
+ I.value = !1;
189
+ }, 0));
190
+ }
191
+ function T() {
192
+ s.value && (s.value = !1, i.value = -1, p.value = "", d("close"));
193
+ }
194
+ function E() {
195
+ return u.value.findIndex((e) => !e.disabled);
196
+ }
197
+ function ue(e, l) {
198
+ const t = u.value;
199
+ if (!t.length)
200
+ return -1;
201
+ let n = e;
202
+ for (let r = 0; r < t.length; r += 1)
203
+ if (n = (n + l + t.length) % t.length, !t[n].disabled)
204
+ return n;
205
+ return -1;
206
+ }
207
+ function C(e) {
208
+ if (i.value = e, e < 0)
209
+ return;
210
+ const l = u.value[e];
211
+ if (!l) {
212
+ i.value = -1;
213
+ return;
214
+ }
215
+ y(`${g(l)}, ${e + 1} of ${u.value.length}.`), Se();
216
+ }
217
+ function Se() {
218
+ z(() => {
219
+ var t;
220
+ const e = ne.value;
221
+ if (!e || !B.value)
222
+ return;
223
+ const l = B.value.querySelector(`#${e}`);
224
+ (t = l == null ? void 0 : l.scrollIntoView) == null || t.call(l, { block: "nearest" });
225
+ });
226
+ }
227
+ async function $e() {
228
+ if (!a.disabled) {
229
+ if (O.value && p.value.trim() && (d("update:modelValue", null), d("change", null)), !s.value) {
230
+ await w();
231
+ return;
232
+ }
233
+ await z(), R(), C(E()), y(G(u.value.length));
234
+ }
235
+ }
236
+ function Be(e) {
237
+ var l;
238
+ if (!a.disabled) {
239
+ if (e.key === "Escape") {
240
+ e.preventDefault(), T(), (l = $.value) == null || l.focus();
241
+ return;
242
+ }
243
+ if ((e.key === "Backspace" || e.key === "Delete") && O.value && !p.value) {
244
+ e.preventDefault(), de();
245
+ return;
246
+ }
247
+ if (e.key === "ArrowDown") {
248
+ if (e.preventDefault(), !s.value) {
249
+ w();
250
+ return;
251
+ }
252
+ if (a.loading)
253
+ return;
254
+ C(ue(i.value, 1));
255
+ return;
256
+ }
257
+ if (e.key === "ArrowUp") {
258
+ if (e.preventDefault(), !s.value) {
259
+ w();
260
+ return;
261
+ }
262
+ if (a.loading)
263
+ return;
264
+ C(ue(i.value < 0 ? u.value.length : i.value, -1));
265
+ return;
266
+ }
267
+ if (e.key === "Enter") {
268
+ if (!s.value || a.loading)
269
+ return;
270
+ e.preventDefault();
271
+ const t = u.value[i.value];
272
+ t && !t.disabled && re(t);
273
+ }
274
+ }
275
+ }
276
+ function re(e) {
277
+ var t;
278
+ if (a.disabled || e.disabled)
279
+ return;
280
+ const l = a.emitValue ? b(e) ?? e : e;
281
+ d("update:modelValue", l), d("change", l), y(`${g(e)} selected.`), T(), (t = $.value) == null || t.focus();
282
+ }
283
+ function de() {
284
+ var e;
285
+ a.disabled || (d("update:modelValue", null), d("change", null), d("clear"), p.value = "", y("Selection cleared."), w(), (e = $.value) == null || e.focus());
286
+ }
287
+ function Ie() {
288
+ window.setTimeout(() => {
289
+ a.closeOnBlur && T(), d("blur");
290
+ }, 100);
291
+ }
292
+ function ce(e) {
293
+ var r, k;
294
+ const l = e.target;
295
+ if (!l || !s.value)
296
+ return;
297
+ if (I.value) {
298
+ I.value = !1;
299
+ return;
300
+ }
301
+ const t = (r = X.value) == null ? void 0 : r.contains(l), n = (k = B.value) == null ? void 0 : k.contains(l);
302
+ !t && !n && T();
303
+ }
304
+ function K() {
305
+ s.value && R();
306
+ }
307
+ return fe(
308
+ () => a.loading,
309
+ (e) => {
310
+ s.value && (i.value = e ? -1 : E(), y(e ? a.loadingText : G(u.value.length)));
311
+ }
312
+ ), fe(u, (e) => {
313
+ var l;
314
+ if (!(!s.value || a.loading)) {
315
+ if (!e.length) {
316
+ i.value = -1, y(a.noResultsText);
317
+ return;
318
+ }
319
+ (i.value < 0 || i.value >= e.length || (l = e[i.value]) != null && l.disabled) && C(E());
320
+ }
321
+ }), Te(() => {
322
+ document.addEventListener("click", ce), window.addEventListener("resize", K), window.addEventListener("scroll", K, !0);
323
+ }), Ee(() => {
324
+ document.removeEventListener("click", ce), window.removeEventListener("resize", K), window.removeEventListener("scroll", K, !0);
325
+ }), (e, l) => (c(), v("div", {
326
+ ref_key: "rootRef",
327
+ ref: X,
328
+ class: J(["select-simple select-simple-search", {
329
+ selected: O.value,
330
+ disabled: o(H),
331
+ "has-error": o(ee),
332
+ open: s.value,
333
+ "has-selected-slot": !!e.$slots.selected && L.value && !p.value
334
+ }])
335
+ }, [
336
+ o(U) ? (c(), v("label", {
337
+ key: 0,
338
+ for: Q.value,
339
+ class: "select-simple-label"
340
+ }, _(o(U)), 9, He)) : x("", !0),
341
+ h("div", {
342
+ ref_key: "controlRef",
343
+ ref: N,
344
+ class: "select-simple-control"
345
+ }, [
346
+ L.value && !p.value ? M(e.$slots, "selected", {
347
+ key: 0,
348
+ option: L.value
349
+ }, void 0, !0) : x("", !0),
350
+ Ke(h("input", {
351
+ id: Q.value,
352
+ ref_key: "inputRef",
353
+ ref: $,
354
+ "onUpdate:modelValue": l[0] || (l[0] = (t) => ae.value = t),
355
+ type: "text",
356
+ class: "select-simple-input",
357
+ role: "combobox",
358
+ name: o(we),
359
+ disabled: o(H),
360
+ placeholder: o(xe),
361
+ "aria-label": o(U) ? void 0 : o(be),
362
+ "aria-expanded": s.value,
363
+ "aria-controls": F.value,
364
+ "aria-activedescendant": ne.value,
365
+ "aria-invalid": o(ee) || void 0,
366
+ "aria-haspopup": "listbox",
367
+ "aria-autocomplete": "list",
368
+ "aria-busy": o(le) || void 0,
369
+ autocomplete: "off",
370
+ onFocus: w,
371
+ onClick: w,
372
+ onInput: $e,
373
+ onKeydown: Be,
374
+ onBlur: Ie
375
+ }, null, 40, Pe), [
376
+ [Me, ae.value]
377
+ ]),
378
+ o(ge) && O.value && !o(H) ? (c(), v("button", {
379
+ key: 1,
380
+ type: "button",
381
+ class: "select-simple-clear",
382
+ "aria-label": o(me),
383
+ onClick: ve(de, ["stop"])
384
+ }, [...l[2] || (l[2] = [
385
+ h("svg", {
386
+ width: "16",
387
+ height: "16",
388
+ viewBox: "0 0 16 16",
389
+ "aria-hidden": "true",
390
+ focusable: "false"
391
+ }, [
392
+ h("path", {
393
+ d: "M12 4L4 12M4 4l8 8",
394
+ fill: "none",
395
+ stroke: "currentColor",
396
+ "stroke-linecap": "round",
397
+ "stroke-width": "2"
398
+ })
399
+ ], -1)
400
+ ])], 8, Ue)) : x("", !0)
401
+ ], 512),
402
+ h("div", qe, _(j.value), 1),
403
+ (c(), ze(Ae, {
404
+ to: "body",
405
+ disabled: o(P)
406
+ }, [
407
+ s.value && !o(P) ? (c(), v("div", We)) : x("", !0),
408
+ s.value ? (c(), v("div", {
409
+ key: 1,
410
+ id: F.value,
411
+ ref_key: "listboxRef",
412
+ ref: B,
413
+ role: "listbox",
414
+ class: J(["select-simple-options", [o(te), o(ye), { "select-simple-options-inline": o(P) }]]),
415
+ style: Fe(Le.value)
416
+ }, [
417
+ o(le) ? (c(), v("div", Ge, [
418
+ M(e.$slots, "loading", {}, () => [
419
+ pe(_(o(he)), 1)
420
+ ], !0)
421
+ ])) : u.value.length ? (c(!0), v(Ne, { key: 1 }, je(u.value, (t, n) => (c(), v("div", {
422
+ id: se(n),
423
+ key: Ve(t, n),
424
+ role: "option",
425
+ class: J(["select-simple-option", [
426
+ o(_e),
427
+ {
428
+ selected: Z(t),
429
+ active: i.value === n,
430
+ disabled: t.disabled,
431
+ highlighted: o(te) === "billing-state" && i.value === n
432
+ }
433
+ ]]),
434
+ "aria-selected": Z(t),
435
+ "aria-disabled": t.disabled || void 0,
436
+ onMousedown: l[1] || (l[1] = ve(() => {
437
+ }, ["prevent"])),
438
+ onClick: (r) => re(t)
439
+ }, [
440
+ M(e.$slots, "option", {
441
+ option: t,
442
+ selected: Z(t),
443
+ active: i.value === n
444
+ }, () => [
445
+ h("span", Qe, [
446
+ h("strong", null, _(g(t)), 1),
447
+ ie(t) ? (c(), v("span", Xe, _(ie(t)), 1)) : x("", !0)
448
+ ])
449
+ ], !0)
450
+ ], 42, Je))), 128)) : (c(), v("div", Ye, [
451
+ M(e.$slots, "no-results", {}, () => [
452
+ pe(_(o(ke)), 1)
453
+ ], !0)
454
+ ]))
455
+ ], 14, Ze)) : x("", !0)
456
+ ], 8, ["disabled"]))
457
+ ], 2));
458
+ }
459
+ }), ll = (V, A) => {
460
+ const a = V.__vccOpts || V;
461
+ for (const [d, S] of A)
462
+ a[d] = S;
463
+ return a;
464
+ }, al = /* @__PURE__ */ ll(el, [["__scopeId", "data-v-58b7f39e"]]);
465
+ export {
466
+ al as SelectSimpleSearch,
467
+ al as default
468
+ };
@@ -0,0 +1 @@
1
+ (function(p,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(p=typeof globalThis<"u"?globalThis:p||self,e(p.AccessibleVueSearchSelect={},p.Vue))})(this,(function(p,e){"use strict";const ae=["for"],oe=["id","name","disabled","placeholder","aria-label","aria-expanded","aria-controls","aria-activedescendant","aria-invalid","aria-busy"],re=["aria-label"],ie={class:"sr-only","aria-live":"polite","aria-atomic":"true"},se={key:0,class:"select-simple-overlay"},ce=["id"],de={key:0,class:"select-simple-status"},ue=["id","aria-selected","aria-disabled","onClick"],fe={class:"select-simple-option-text"},pe={key:0,class:"select-simple-option-description"},me={key:2,class:"select-simple-status"},F=((w,I)=>{const a=w.__vccOpts||w;for(const[d,v]of I)a[d]=v;return a})(e.defineComponent({__name:"SelectSimpleSearch",props:{options:{default:()=>[]},modelValue:{default:null},placeholder:{default:"Select an option"},type:{default:"default"},inlineListbox:{type:Boolean,default:!1},closeOnBlur:{type:Boolean,default:!1},label:{default:""},name:{default:"select-simple-search"},inputId:{default:""},listboxId:{default:""},ariaLabel:{default:"Search options"},labelKey:{default:""},valueKey:{default:""},searchKeys:{default:()=>["label","name","public_title","address.city","address.full_address","slug","value","id"]},disabled:{type:Boolean,default:!1},clearable:{type:Boolean,default:!0},clearLabel:{default:"Clear selection"},noResultsText:{default:"No results found"},loading:{type:Boolean,default:!1},loadingText:{default:"Loading options..."},maxHeight:{default:"18rem"},dropdownClass:{default:""},optionClass:{default:""},hasError:{type:Boolean,default:!1},facilityFallbackLabel:{default:"Storage Facility"},emitValue:{type:Boolean,default:!1},sortOptions:{type:Boolean,default:!1}},emits:["update:modelValue","change","blur","clear","open","close"],setup(w,{emit:I}){const a=w,d=I,v=e.useId().replace(/[^A-Za-z0-9_-]/g,"-"),D=e.computed(()=>a.listboxId||`select-simple-listbox-${v}`),P=e.computed(()=>a.inputId||`select-simple-input-${v}`),H=e.ref(null),O=e.ref(null),_=e.ref(null),B=e.ref(null),i=e.ref(!1),u=e.ref(""),r=e.ref(-1),T=e.ref(""),U=e.ref({}),x=e.ref(!1),{ariaLabel:be,clearLabel:ge,clearable:ye,disabled:N,dropdownClass:he,hasError:q,inlineListbox:R,label:K,loading:W,loadingText:ke,name:we,noResultsText:ve,optionClass:_e,placeholder:Be,type:Z}=e.toRefs(a),xe=e.computed(()=>{const t={maxHeight:a.maxHeight};return a.inlineListbox?t:{...t,...U.value}}),h=e.computed(()=>{const t=a.modelValue;if(t==null)return null;if(Q(t)){const l=f(t);return a.options.find(n=>V(f(n),l))??t}return a.options.find(l=>V(f(l),t))??null}),Se=e.computed(()=>h.value?m(h.value):""),S=e.computed(()=>a.modelValue!==null&&a.modelValue!==void 0),G=e.computed({get(){return u.value||Se.value},set(t){u.value=t}}),s=e.computed(()=>{const t=u.value.trim().toLowerCase(),l=t?a.options.filter(n=>a.searchKeys.some(o=>z(M(n,o)).toLowerCase().includes(t))):a.options;return a.sortOptions?[...l].sort((n,o)=>{const c=m(n).toLowerCase(),y=m(o).toLowerCase();return c.localeCompare(y)}):[...l]}),J=e.computed(()=>{if(!(a.loading||!i.value||r.value<0||r.value>=s.value.length))return Y(r.value)});function Q(t){return typeof t=="object"&&t!==null}function M(t,l){return l.split(".").reduce((n,o)=>{if(n&&typeof n=="object"&&o in n)return n[o]},t)}function z(t){return t==null?"":String(t)}function f(t){if(a.valueKey){const l=M(t,a.valueKey);if(typeof l=="string"||typeof l=="number")return l}return t.slug??t.value??t.id}function m(t){var l,n,o;if(a.labelKey){const c=z(M(t,a.labelKey)).trim();if(c)return c}if(t.label)return t.label;if(t.name)return t.name;if((l=t.address)!=null&&l.city||t.public_title){const c=(o=(n=t.address)==null?void 0:n.city)==null?void 0:o.trim(),y=t.public_title&&t.public_title.length>1?t.public_title:a.facilityFallbackLabel;return[c,y].filter(Boolean).join(" - ")}return z(f(t))}function X(t){var l;return((l=t.address)==null?void 0:l.full_address)??""}function Ve(t,l){const n=f(t);return n!==void 0?String(n):`${m(t)}-${l}`}function Y(t){return`${D.value}-option-${t}`}function V(t,l){return t===void 0||l===void 0?!1:String(t)===String(l)}function A(t){const l=a.modelValue;if(l==null)return!1;if(Q(l)){const n=f(l),o=f(t);return V(n,o)||l===t}return V(f(t),l)}function b(t){T.value="",e.nextTick(()=>{T.value=t})}function j(t){return`${t} ${t===1?"option":"options"} available.`}function C(){if(a.inlineListbox||!O.value)return;const t=O.value.getBoundingClientRect();U.value={left:`${t.left}px`,top:`${t.bottom+4}px`,width:`${t.width}px`}}async function g(){a.disabled||i.value||(x.value=!0,C(),i.value=!0,d("open"),await e.nextTick(),C(),k(a.loading?-1:E()),b(a.loading?a.loadingText:j(s.value.length)),window.setTimeout(()=>{x.value=!1},0))}function L(){i.value&&(i.value=!1,r.value=-1,u.value="",d("close"))}function E(){return s.value.findIndex(t=>!t.disabled)}function ee(t,l){const n=s.value;if(!n.length)return-1;let o=t;for(let c=0;c<n.length;c+=1)if(o=(o+l+n.length)%n.length,!n[o].disabled)return o;return-1}function k(t){if(r.value=t,t<0)return;const l=s.value[t];if(!l){r.value=-1;return}b(`${m(l)}, ${t+1} of ${s.value.length}.`),Ce()}function Ce(){e.nextTick(()=>{var n;const t=J.value;if(!t||!B.value)return;const l=B.value.querySelector(`#${t}`);(n=l==null?void 0:l.scrollIntoView)==null||n.call(l,{block:"nearest"})})}async function Le(){if(!a.disabled){if(S.value&&u.value.trim()&&(d("update:modelValue",null),d("change",null)),!i.value){await g();return}await e.nextTick(),C(),k(E()),b(j(s.value.length))}}function Ee(t){var l;if(!a.disabled){if(t.key==="Escape"){t.preventDefault(),L(),(l=_.value)==null||l.focus();return}if((t.key==="Backspace"||t.key==="Delete")&&S.value&&!u.value){t.preventDefault(),le();return}if(t.key==="ArrowDown"){if(t.preventDefault(),!i.value){g();return}if(a.loading)return;k(ee(r.value,1));return}if(t.key==="ArrowUp"){if(t.preventDefault(),!i.value){g();return}if(a.loading)return;k(ee(r.value<0?s.value.length:r.value,-1));return}if(t.key==="Enter"){if(!i.value||a.loading)return;t.preventDefault();const n=s.value[r.value];n&&!n.disabled&&te(n)}}}function te(t){var n;if(a.disabled||t.disabled)return;const l=a.emitValue?f(t)??t:t;d("update:modelValue",l),d("change",l),b(`${m(t)} selected.`),L(),(n=_.value)==null||n.focus()}function le(){var t;a.disabled||(d("update:modelValue",null),d("change",null),d("clear"),u.value="",b("Selection cleared."),g(),(t=_.value)==null||t.focus())}function $e(){window.setTimeout(()=>{a.closeOnBlur&&L(),d("blur")},100)}function ne(t){var c,y;const l=t.target;if(!l||!i.value)return;if(x.value){x.value=!1;return}const n=(c=H.value)==null?void 0:c.contains(l),o=(y=B.value)==null?void 0:y.contains(l);!n&&!o&&L()}function $(){i.value&&C()}return e.watch(()=>a.loading,t=>{i.value&&(r.value=t?-1:E(),b(t?a.loadingText:j(s.value.length)))}),e.watch(s,t=>{var l;if(!(!i.value||a.loading)){if(!t.length){r.value=-1,b(a.noResultsText);return}(r.value<0||r.value>=t.length||(l=t[r.value])!=null&&l.disabled)&&k(E())}}),e.onMounted(()=>{document.addEventListener("click",ne),window.addEventListener("resize",$),window.addEventListener("scroll",$,!0)}),e.onUnmounted(()=>{document.removeEventListener("click",ne),window.removeEventListener("resize",$),window.removeEventListener("scroll",$,!0)}),(t,l)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"rootRef",ref:H,class:e.normalizeClass(["select-simple select-simple-search",{selected:S.value,disabled:e.unref(N),"has-error":e.unref(q),open:i.value,"has-selected-slot":!!t.$slots.selected&&h.value&&!u.value}])},[e.unref(K)?(e.openBlock(),e.createElementBlock("label",{key:0,for:P.value,class:"select-simple-label"},e.toDisplayString(e.unref(K)),9,ae)):e.createCommentVNode("",!0),e.createElementVNode("div",{ref_key:"controlRef",ref:O,class:"select-simple-control"},[h.value&&!u.value?e.renderSlot(t.$slots,"selected",{key:0,option:h.value},void 0,!0):e.createCommentVNode("",!0),e.withDirectives(e.createElementVNode("input",{id:P.value,ref_key:"inputRef",ref:_,"onUpdate:modelValue":l[0]||(l[0]=n=>G.value=n),type:"text",class:"select-simple-input",role:"combobox",name:e.unref(we),disabled:e.unref(N),placeholder:e.unref(Be),"aria-label":e.unref(K)?void 0:e.unref(be),"aria-expanded":i.value,"aria-controls":D.value,"aria-activedescendant":J.value,"aria-invalid":e.unref(q)||void 0,"aria-haspopup":"listbox","aria-autocomplete":"list","aria-busy":e.unref(W)||void 0,autocomplete:"off",onFocus:g,onClick:g,onInput:Le,onKeydown:Ee,onBlur:$e},null,40,oe),[[e.vModelText,G.value]]),e.unref(ye)&&S.value&&!e.unref(N)?(e.openBlock(),e.createElementBlock("button",{key:1,type:"button",class:"select-simple-clear","aria-label":e.unref(ge),onClick:e.withModifiers(le,["stop"])},[...l[2]||(l[2]=[e.createElementVNode("svg",{width:"16",height:"16",viewBox:"0 0 16 16","aria-hidden":"true",focusable:"false"},[e.createElementVNode("path",{d:"M12 4L4 12M4 4l8 8",fill:"none",stroke:"currentColor","stroke-linecap":"round","stroke-width":"2"})],-1)])],8,re)):e.createCommentVNode("",!0)],512),e.createElementVNode("div",ie,e.toDisplayString(T.value),1),(e.openBlock(),e.createBlock(e.Teleport,{to:"body",disabled:e.unref(R)},[i.value&&!e.unref(R)?(e.openBlock(),e.createElementBlock("div",se)):e.createCommentVNode("",!0),i.value?(e.openBlock(),e.createElementBlock("div",{key:1,id:D.value,ref_key:"listboxRef",ref:B,role:"listbox",class:e.normalizeClass(["select-simple-options",[e.unref(Z),e.unref(he),{"select-simple-options-inline":e.unref(R)}]]),style:e.normalizeStyle(xe.value)},[e.unref(W)?(e.openBlock(),e.createElementBlock("div",de,[e.renderSlot(t.$slots,"loading",{},()=>[e.createTextVNode(e.toDisplayString(e.unref(ke)),1)],!0)])):s.value.length?(e.openBlock(!0),e.createElementBlock(e.Fragment,{key:1},e.renderList(s.value,(n,o)=>(e.openBlock(),e.createElementBlock("div",{id:Y(o),key:Ve(n,o),role:"option",class:e.normalizeClass(["select-simple-option",[e.unref(_e),{selected:A(n),active:r.value===o,disabled:n.disabled,highlighted:e.unref(Z)==="billing-state"&&r.value===o}]]),"aria-selected":A(n),"aria-disabled":n.disabled||void 0,onMousedown:l[1]||(l[1]=e.withModifiers(()=>{},["prevent"])),onClick:c=>te(n)},[e.renderSlot(t.$slots,"option",{option:n,selected:A(n),active:r.value===o},()=>[e.createElementVNode("span",fe,[e.createElementVNode("strong",null,e.toDisplayString(m(n)),1),X(n)?(e.openBlock(),e.createElementBlock("span",pe,e.toDisplayString(X(n)),1)):e.createCommentVNode("",!0)])],!0)],42,ue))),128)):(e.openBlock(),e.createElementBlock("div",me,[e.renderSlot(t.$slots,"no-results",{},()=>[e.createTextVNode(e.toDisplayString(e.unref(ve)),1)],!0)]))],14,ce)):e.createCommentVNode("",!0)],8,["disabled"]))],2))}}),[["__scopeId","data-v-58b7f39e"]]);p.SelectSimpleSearch=F,p.default=F,Object.defineProperties(p,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
@@ -0,0 +1,79 @@
1
+ import { SelectOption, SelectSimpleSearchProps } from '../types';
2
+ declare function __VLS_template(): {
3
+ attrs: Partial<{}>;
4
+ slots: {
5
+ selected?(_: {
6
+ option: SelectOption;
7
+ }): any;
8
+ loading?(_: {}): any;
9
+ option?(_: {
10
+ option: SelectOption;
11
+ selected: boolean;
12
+ active: boolean;
13
+ }): any;
14
+ 'no-results'?(_: {}): any;
15
+ };
16
+ refs: {
17
+ rootRef: HTMLDivElement;
18
+ controlRef: HTMLDivElement;
19
+ inputRef: HTMLInputElement;
20
+ listboxRef: HTMLDivElement;
21
+ };
22
+ rootEl: HTMLDivElement;
23
+ };
24
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
25
+ declare const __VLS_component: import('vue').DefineComponent<SelectSimpleSearchProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
26
+ "update:modelValue": (value: import('..').ModelValue) => any;
27
+ change: (value: import('..').ModelValue) => any;
28
+ blur: () => any;
29
+ clear: () => any;
30
+ open: () => any;
31
+ close: () => any;
32
+ }, string, import('vue').PublicProps, Readonly<SelectSimpleSearchProps> & Readonly<{
33
+ "onUpdate:modelValue"?: ((value: import('..').ModelValue) => any) | undefined;
34
+ onChange?: ((value: import('..').ModelValue) => any) | undefined;
35
+ onBlur?: (() => any) | undefined;
36
+ onClear?: (() => any) | undefined;
37
+ onOpen?: (() => any) | undefined;
38
+ onClose?: (() => any) | undefined;
39
+ }>, {
40
+ label: string;
41
+ name: string;
42
+ disabled: boolean;
43
+ options: SelectOption[];
44
+ modelValue: import('..').ModelValue;
45
+ placeholder: string;
46
+ type: string;
47
+ inlineListbox: boolean;
48
+ closeOnBlur: boolean;
49
+ inputId: string;
50
+ listboxId: string;
51
+ ariaLabel: string;
52
+ labelKey: string;
53
+ valueKey: string;
54
+ searchKeys: string[];
55
+ clearable: boolean;
56
+ clearLabel: string;
57
+ noResultsText: string;
58
+ loading: boolean;
59
+ loadingText: string;
60
+ maxHeight: string;
61
+ dropdownClass: string;
62
+ optionClass: string;
63
+ hasError: boolean;
64
+ facilityFallbackLabel: string;
65
+ emitValue: boolean;
66
+ sortOptions: boolean;
67
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
68
+ rootRef: HTMLDivElement;
69
+ controlRef: HTMLDivElement;
70
+ inputRef: HTMLInputElement;
71
+ listboxRef: HTMLDivElement;
72
+ }, HTMLDivElement>;
73
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
74
+ export default _default;
75
+ type __VLS_WithTemplateSlots<T, S> = T & {
76
+ new (): {
77
+ $slots: S;
78
+ };
79
+ };
@@ -0,0 +1,4 @@
1
+ import { default as SelectSimpleSearch } from './components/SelectSimpleSearch.vue';
2
+ export { SelectSimpleSearch };
3
+ export default SelectSimpleSearch;
4
+ export type { ModelValue, SelectOption, SelectPrimitive, SelectSimpleSearchEmits, SelectSimpleSearchProps, } from './types';
@@ -0,0 +1,53 @@
1
+ export type SelectPrimitive = string | number;
2
+ export interface SelectOption {
3
+ id?: SelectPrimitive;
4
+ value?: SelectPrimitive;
5
+ slug?: string;
6
+ label?: string;
7
+ name?: string;
8
+ disabled?: boolean;
9
+ address?: {
10
+ city?: string;
11
+ full_address?: string;
12
+ };
13
+ public_title?: string;
14
+ [key: string]: unknown;
15
+ }
16
+ export type ModelValue = SelectOption | SelectPrimitive | null;
17
+ export interface SelectSimpleSearchProps {
18
+ options?: SelectOption[];
19
+ modelValue?: ModelValue;
20
+ placeholder?: string;
21
+ type?: string;
22
+ inlineListbox?: boolean;
23
+ closeOnBlur?: boolean;
24
+ label?: string;
25
+ name?: string;
26
+ inputId?: string;
27
+ listboxId?: string;
28
+ ariaLabel?: string;
29
+ labelKey?: string;
30
+ valueKey?: string;
31
+ searchKeys?: string[];
32
+ disabled?: boolean;
33
+ clearable?: boolean;
34
+ clearLabel?: string;
35
+ noResultsText?: string;
36
+ loading?: boolean;
37
+ loadingText?: string;
38
+ maxHeight?: string;
39
+ dropdownClass?: string;
40
+ optionClass?: string;
41
+ hasError?: boolean;
42
+ facilityFallbackLabel?: string;
43
+ emitValue?: boolean;
44
+ sortOptions?: boolean;
45
+ }
46
+ export interface SelectSimpleSearchEmits {
47
+ (event: "update:modelValue", value: ModelValue): void;
48
+ (event: "change", value: ModelValue): void;
49
+ (event: "blur"): void;
50
+ (event: "clear"): void;
51
+ (event: "open"): void;
52
+ (event: "close"): void;
53
+ }
package/package.json ADDED
@@ -0,0 +1,302 @@
1
+ {
2
+ "name": "@irina_grigoreva/accessible-vue-search-select",
3
+ "version": "1.0.0",
4
+ "description": "Accessible Vue 3 searchable select component with keyboard navigation, ARIA combobox/listbox support, TypeScript, slots, and teleported dropdowns.",
5
+ "keywords": [
6
+ "vue",
7
+ "vue3",
8
+ "typescript",
9
+ "accessibility",
10
+ "a11y",
11
+ "combobox",
12
+ "listbox",
13
+ "select",
14
+ "searchable-select",
15
+ "ui-component"
16
+ ],
17
+ "homepage": "https://github.com/irina-grigoreva/accessible-vue-search-select#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/irina-grigoreva/accessible-vue-search-select/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/irina-grigoreva/accessible-vue-search-select.git"
24
+ },
25
+ "license": "MIT",
26
+ "author": "Irina Grigoreva",
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/accessible-vue-search-select.js",
32
+ "require": "./dist/accessible-vue-search-select.umd.cjs"
33
+ },
34
+ "./dist/accessible-vue-search-select.css": "./dist/accessible-vue-search-select.css",
35
+ "./style.css": "./dist/accessible-vue-search-select.css"
36
+ },
37
+ "main": "./dist/accessible-vue-search-select.umd.cjs",
38
+ "types": "./dist/index.d.ts",
39
+ "directories": {
40
+ "doc": "docs",
41
+ "example": "examples",
42
+ "test": "tests"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md",
47
+ "LICENSE"
48
+ ],
49
+ "scripts": {
50
+ "build": "vue-tsc --noEmit && vite build",
51
+ "dev": "vite",
52
+ "lint": "eslint .",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "typecheck": "vue-tsc --noEmit",
56
+ "preview": "vite preview",
57
+ "prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build"
58
+ },
59
+ "dependencies": {
60
+ "abbrev": "^2.0.0",
61
+ "acorn": "^8.16.0",
62
+ "acorn-jsx": "^5.3.2",
63
+ "agent-base": "^7.1.4",
64
+ "ajv": "^6.15.0",
65
+ "ajv-formats": "^3.0.1",
66
+ "alien-signals": "^1.0.13",
67
+ "ansi-regex": "^6.2.2",
68
+ "ansi-styles": "^6.2.3",
69
+ "argparse": "^2.0.1",
70
+ "assertion-error": "^2.0.1",
71
+ "asynckit": "^0.4.0",
72
+ "balanced-match": "^1.0.2",
73
+ "boolbase": "^1.0.0",
74
+ "brace-expansion": "^2.1.0",
75
+ "cac": "^6.7.14",
76
+ "call-bind-apply-helpers": "^1.0.2",
77
+ "callsites": "^3.1.0",
78
+ "chai": "^5.3.3",
79
+ "chalk": "^4.1.2",
80
+ "check-error": "^2.1.3",
81
+ "color-convert": "^2.0.1",
82
+ "color-name": "^1.1.4",
83
+ "combined-stream": "^1.0.8",
84
+ "commander": "^10.0.1",
85
+ "compare-versions": "^6.1.1",
86
+ "concat-map": "^0.0.1",
87
+ "confbox": "^0.2.4",
88
+ "config-chain": "^1.1.13",
89
+ "cross-spawn": "^7.0.6",
90
+ "cssesc": "^3.0.0",
91
+ "cssstyle": "^4.6.0",
92
+ "csstype": "^3.2.3",
93
+ "data-urls": "^5.0.0",
94
+ "de-indent": "^1.0.2",
95
+ "debug": "^4.4.3",
96
+ "decimal.js": "^10.6.0",
97
+ "deep-eql": "^5.0.2",
98
+ "deep-is": "^0.1.4",
99
+ "delayed-stream": "^1.0.0",
100
+ "diff": "^8.0.4",
101
+ "dunder-proto": "^1.0.1",
102
+ "eastasianwidth": "^0.2.0",
103
+ "editorconfig": "^1.0.7",
104
+ "emoji-regex": "^9.2.2",
105
+ "entities": "^7.0.1",
106
+ "es-define-property": "^1.0.1",
107
+ "es-errors": "^1.3.0",
108
+ "es-module-lexer": "^1.7.0",
109
+ "es-object-atoms": "^1.1.1",
110
+ "es-set-tostringtag": "^2.1.0",
111
+ "esbuild": "^0.25.12",
112
+ "escape-string-regexp": "^4.0.0",
113
+ "eslint-scope": "^8.4.0",
114
+ "eslint-visitor-keys": "^4.2.1",
115
+ "espree": "^10.4.0",
116
+ "esquery": "^1.7.0",
117
+ "esrecurse": "^4.3.0",
118
+ "estraverse": "^5.3.0",
119
+ "estree-walker": "^2.0.2",
120
+ "esutils": "^2.0.3",
121
+ "expect-type": "^1.3.0",
122
+ "exsolve": "^1.0.8",
123
+ "fast-deep-equal": "^3.1.3",
124
+ "fast-json-stable-stringify": "^2.1.0",
125
+ "fast-levenshtein": "^2.0.6",
126
+ "fast-uri": "^3.1.2",
127
+ "fdir": "^6.5.0",
128
+ "file-entry-cache": "^8.0.0",
129
+ "find-up": "^5.0.0",
130
+ "flat-cache": "^4.0.1",
131
+ "flatted": "^3.4.2",
132
+ "foreground-child": "^3.3.1",
133
+ "form-data": "^4.0.5",
134
+ "fs-extra": "^11.3.5",
135
+ "function-bind": "^1.1.2",
136
+ "get-intrinsic": "^1.3.0",
137
+ "get-proto": "^1.0.1",
138
+ "glob": "^10.5.0",
139
+ "glob-parent": "^6.0.2",
140
+ "globals": "^14.0.0",
141
+ "gopd": "^1.2.0",
142
+ "graceful-fs": "^4.2.11",
143
+ "has-flag": "^4.0.0",
144
+ "has-symbols": "^1.1.0",
145
+ "has-tostringtag": "^1.0.2",
146
+ "hasown": "^2.0.3",
147
+ "he": "^1.2.0",
148
+ "html-encoding-sniffer": "^4.0.0",
149
+ "http-proxy-agent": "^7.0.2",
150
+ "https-proxy-agent": "^7.0.6",
151
+ "iconv-lite": "^0.6.3",
152
+ "ignore": "^5.3.2",
153
+ "import-fresh": "^3.3.1",
154
+ "import-lazy": "^4.0.0",
155
+ "imurmurhash": "^0.1.4",
156
+ "ini": "^1.3.8",
157
+ "is-core-module": "^2.16.2",
158
+ "is-extglob": "^2.1.1",
159
+ "is-fullwidth-code-point": "^3.0.0",
160
+ "is-glob": "^4.0.3",
161
+ "is-potential-custom-element-name": "^1.0.1",
162
+ "isexe": "^2.0.0",
163
+ "jackspeak": "^3.4.3",
164
+ "jju": "^1.4.0",
165
+ "js-beautify": "^1.15.4",
166
+ "js-cookie": "^3.0.7",
167
+ "js-tokens": "^9.0.1",
168
+ "js-yaml": "^4.1.1",
169
+ "json-buffer": "^3.0.1",
170
+ "json-schema-traverse": "^0.4.1",
171
+ "json-stable-stringify-without-jsonify": "^1.0.1",
172
+ "jsonfile": "^6.2.1",
173
+ "keyv": "^4.5.4",
174
+ "kolorist": "^1.8.0",
175
+ "levn": "^0.4.1",
176
+ "local-pkg": "^1.1.2",
177
+ "locate-path": "^6.0.0",
178
+ "lodash": "^4.18.1",
179
+ "lodash.merge": "^4.6.2",
180
+ "loupe": "^3.2.1",
181
+ "lru-cache": "^10.4.3",
182
+ "magic-string": "^0.30.21",
183
+ "math-intrinsics": "^1.1.0",
184
+ "mime-db": "^1.52.0",
185
+ "mime-types": "^2.1.35",
186
+ "minimatch": "^9.0.9",
187
+ "minipass": "^7.1.3",
188
+ "mlly": "^1.8.2",
189
+ "ms": "^2.1.3",
190
+ "muggle-string": "^0.4.1",
191
+ "nanoid": "^3.3.12",
192
+ "natural-compare": "^1.4.0",
193
+ "nopt": "^7.2.1",
194
+ "nth-check": "^2.1.1",
195
+ "nwsapi": "^2.2.23",
196
+ "optionator": "^0.9.4",
197
+ "p-limit": "^3.1.0",
198
+ "p-locate": "^5.0.0",
199
+ "package-json-from-dist": "^1.0.1",
200
+ "parent-module": "^1.0.1",
201
+ "parse5": "^7.3.0",
202
+ "path-browserify": "^1.0.1",
203
+ "path-exists": "^4.0.0",
204
+ "path-key": "^3.1.1",
205
+ "path-parse": "^1.0.7",
206
+ "path-scurry": "^1.11.1",
207
+ "pathe": "^2.0.3",
208
+ "pathval": "^2.0.1",
209
+ "picocolors": "^1.1.1",
210
+ "picomatch": "^4.0.4",
211
+ "pkg-types": "^2.3.1",
212
+ "postcss": "^8.5.14",
213
+ "postcss-selector-parser": "^6.1.2",
214
+ "prelude-ls": "^1.2.1",
215
+ "proto-list": "^1.2.4",
216
+ "punycode": "^2.3.1",
217
+ "quansync": "^0.2.11",
218
+ "require-from-string": "^2.0.2",
219
+ "resolve": "^1.22.12",
220
+ "resolve-from": "^4.0.0",
221
+ "rollup": "^4.60.4",
222
+ "rrweb-cssom": "^0.7.1",
223
+ "safer-buffer": "^2.1.2",
224
+ "saxes": "^6.0.0",
225
+ "semver": "^7.8.0",
226
+ "shebang-command": "^2.0.0",
227
+ "shebang-regex": "^3.0.0",
228
+ "siginfo": "^2.0.0",
229
+ "signal-exit": "^4.1.0",
230
+ "source-map": "^0.6.1",
231
+ "source-map-js": "^1.2.1",
232
+ "sprintf-js": "^1.0.3",
233
+ "stackback": "^0.0.2",
234
+ "std-env": "^3.10.0",
235
+ "string-argv": "^0.3.2",
236
+ "string-width": "^5.1.2",
237
+ "string-width-cjs": "^4.2.3",
238
+ "strip-ansi": "^7.2.0",
239
+ "strip-ansi-cjs": "^6.0.1",
240
+ "strip-json-comments": "^3.1.1",
241
+ "strip-literal": "^3.1.0",
242
+ "supports-color": "^7.2.0",
243
+ "supports-preserve-symlinks-flag": "^1.0.0",
244
+ "symbol-tree": "^3.2.4",
245
+ "tinybench": "^2.9.0",
246
+ "tinyexec": "^0.3.2",
247
+ "tinyglobby": "^0.2.16",
248
+ "tinypool": "^1.1.1",
249
+ "tinyrainbow": "^2.0.0",
250
+ "tinyspy": "^4.0.4",
251
+ "tldts": "^6.1.86",
252
+ "tldts-core": "^6.1.86",
253
+ "tough-cookie": "^5.1.2",
254
+ "tr46": "^5.1.1",
255
+ "ts-api-utils": "^2.5.0",
256
+ "type-check": "^0.4.0",
257
+ "type-fest": "^0.20.2",
258
+ "ufo": "^1.6.4",
259
+ "undici-types": "^7.24.6",
260
+ "universalify": "^2.0.1",
261
+ "uri-js": "^4.4.1",
262
+ "util-deprecate": "^1.0.2",
263
+ "vite-node": "^3.2.4",
264
+ "vscode-uri": "^3.1.0",
265
+ "vue-component-type-helpers": "^3.3.0",
266
+ "vue-eslint-parser": "^9.4.3",
267
+ "w3c-xmlserializer": "^5.0.0",
268
+ "webidl-conversions": "^7.0.0",
269
+ "whatwg-encoding": "^3.1.1",
270
+ "whatwg-mimetype": "^4.0.0",
271
+ "whatwg-url": "^14.2.0",
272
+ "which": "^2.0.2",
273
+ "why-is-node-running": "^2.3.0",
274
+ "word-wrap": "^1.2.5",
275
+ "wrap-ansi": "^8.1.0",
276
+ "wrap-ansi-cjs": "^7.0.0",
277
+ "ws": "^8.20.1",
278
+ "xml-name-validator": "^5.0.0",
279
+ "xmlchars": "^2.2.0",
280
+ "yocto-queue": "^0.1.0"
281
+ },
282
+ "devDependencies": {
283
+ "@eslint/js": "^9.27.0",
284
+ "@types/node": "^25.9.0",
285
+ "@vitejs/plugin-vue": "^5.2.4",
286
+ "@vue/test-utils": "^2.4.6",
287
+ "eslint": "^9.27.0",
288
+ "eslint-plugin-vue": "^9.33.0",
289
+ "jsdom": "^25.0.1",
290
+ "typescript": "^5.8.3",
291
+ "typescript-eslint": "^8.32.1",
292
+ "vite": "^6.3.5",
293
+ "vite-plugin-dts": "^4.5.4",
294
+ "vitest": "^3.1.4",
295
+ "vue": "^3.5.16",
296
+ "vue-tsc": "^2.2.10"
297
+ },
298
+ "peerDependencies": {
299
+ "vue": "^3.5.0"
300
+ },
301
+ "module": "./dist/accessible-vue-search-select.js"
302
+ }