@referralgps/selectra 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.
@@ -0,0 +1,1318 @@
1
+ const DIACRITICS = {
2
+ a: "[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]",
3
+ b: "[b␢βΒB฿𐌁ᛒ]",
4
+ c: "[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]",
5
+ d: "[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]",
6
+ e: "[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]",
7
+ f: "[fƑƒḞḟ]",
8
+ g: "[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]",
9
+ h: "[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]",
10
+ i: "[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]",
11
+ j: "[jȷĴĵɈɉʝɟʲ]",
12
+ k: "[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]",
13
+ l: "[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]",
14
+ n: "[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]",
15
+ o: "[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]",
16
+ p: "[pṔṕṖṗⱣᵽƤƥᵱ]",
17
+ q: "[qꝖꝗʠɊɋꝘꝙq̃]",
18
+ r: "[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]",
19
+ s: "[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]",
20
+ t: "[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]",
21
+ u: "[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]",
22
+ v: "[vṼṽṾṿƲʋꝞꝟⱱʋ]",
23
+ w: "[wẂẃẀẁŴŵẄẅẆẇẈẉ]",
24
+ x: "[xẌẍẊẋχ]",
25
+ y: "[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]",
26
+ z: "[zŹźẐẑŽžŻżẒẓẔẕƵƶ]"
27
+ };
28
+ const asciifold = (() => {
29
+ const lookup = {};
30
+ let i18nChars = "";
31
+ for (const k in DIACRITICS) {
32
+ const chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
33
+ i18nChars += chunk;
34
+ for (let i = 0; i < chunk.length; i++) {
35
+ lookup[chunk.charAt(i)] = k;
36
+ }
37
+ }
38
+ const regexp = new RegExp("[" + i18nChars + "]", "g");
39
+ return (str) => str.replace(regexp, (ch) => lookup[ch] || "").toLowerCase();
40
+ })();
41
+ function escapeRegex(str) {
42
+ return (str + "").replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
43
+ }
44
+ function getAttr(obj, name, nesting) {
45
+ if (!obj || !name) return void 0;
46
+ if (!nesting) return obj[name];
47
+ const names = name.split(".");
48
+ let current = obj;
49
+ while (names.length && current) {
50
+ current = current[names.shift()];
51
+ }
52
+ return current;
53
+ }
54
+ function cmp(a, b) {
55
+ if (typeof a === "number" && typeof b === "number") {
56
+ return a > b ? 1 : a < b ? -1 : 0;
57
+ }
58
+ a = asciifold(String(a || ""));
59
+ b = asciifold(String(b || ""));
60
+ if (a > b) return 1;
61
+ if (b > a) return -1;
62
+ return 0;
63
+ }
64
+ class Sifter {
65
+ constructor(items, settings = {}) {
66
+ this.items = items;
67
+ this.settings = { diacritics: true, ...settings };
68
+ }
69
+ tokenize(query, respectWordBoundaries) {
70
+ query = String(query || "").toLowerCase().trim();
71
+ if (!query.length) return [];
72
+ const tokens = [];
73
+ const words = query.split(/\s+/);
74
+ for (const word of words) {
75
+ let regex = escapeRegex(word);
76
+ if (this.settings.diacritics) {
77
+ for (const letter in DIACRITICS) {
78
+ regex = regex.replace(new RegExp(letter, "g"), DIACRITICS[letter]);
79
+ }
80
+ }
81
+ if (respectWordBoundaries) regex = "\\b" + regex;
82
+ tokens.push({ string: word, regex: new RegExp(regex, "i") });
83
+ }
84
+ return tokens;
85
+ }
86
+ getScoreFunction(search, options) {
87
+ search = this.prepareSearch(search, options);
88
+ const { tokens } = search;
89
+ const { fields } = search.options;
90
+ const tokenCount = tokens.length;
91
+ const { nesting } = search.options;
92
+ const scoreValue = (value, token) => {
93
+ if (!value) return 0;
94
+ value = String(value || "");
95
+ const pos = value.search(token.regex);
96
+ if (pos === -1) return 0;
97
+ let score = token.string.length / value.length;
98
+ if (pos === 0) score += 0.5;
99
+ return score;
100
+ };
101
+ const fieldCount = fields.length;
102
+ const scoreObject = fieldCount === 0 ? () => 0 : fieldCount === 1 ? (token, data) => scoreValue(getAttr(data, fields[0], nesting), token) : (token, data) => {
103
+ let sum = 0;
104
+ for (let i = 0; i < fieldCount; i++) {
105
+ sum += scoreValue(getAttr(data, fields[i], nesting), token);
106
+ }
107
+ return sum / fieldCount;
108
+ };
109
+ if (!tokenCount) return () => 0;
110
+ if (tokenCount === 1) return (data) => scoreObject(tokens[0], data);
111
+ if (search.options.conjunction === "and") {
112
+ return (data) => {
113
+ let sum = 0;
114
+ for (let i = 0; i < tokenCount; i++) {
115
+ const score = scoreObject(tokens[i], data);
116
+ if (score <= 0) return 0;
117
+ sum += score;
118
+ }
119
+ return sum / tokenCount;
120
+ };
121
+ }
122
+ return (data) => {
123
+ let sum = 0;
124
+ for (let i = 0; i < tokenCount; i++) {
125
+ sum += scoreObject(tokens[i], data);
126
+ }
127
+ return sum / tokenCount;
128
+ };
129
+ }
130
+ getSortFunction(search, options) {
131
+ search = this.prepareSearch(search, options);
132
+ const sort = !search.query && options.sort_empty || options.sort;
133
+ const getField = (name, result) => {
134
+ if (name === "$score") return result.score;
135
+ return getAttr(this.items[result.id], name, options.nesting);
136
+ };
137
+ const fields = [];
138
+ if (sort) {
139
+ for (const s of sort) {
140
+ if (search.query || s.field !== "$score") {
141
+ fields.push(s);
142
+ }
143
+ }
144
+ }
145
+ if (search.query) {
146
+ let implicitScore = true;
147
+ for (const f of fields) {
148
+ if (f.field === "$score") {
149
+ implicitScore = false;
150
+ break;
151
+ }
152
+ }
153
+ if (implicitScore) fields.unshift({ field: "$score", direction: "desc" });
154
+ } else {
155
+ const idx = fields.findIndex((f) => f.field === "$score");
156
+ if (idx !== -1) fields.splice(idx, 1);
157
+ }
158
+ const multipliers = fields.map((f) => f.direction === "desc" ? -1 : 1);
159
+ const fieldCount = fields.length;
160
+ if (!fieldCount) return null;
161
+ if (fieldCount === 1) {
162
+ const field = fields[0].field;
163
+ const mult = multipliers[0];
164
+ return (a, b) => mult * cmp(getField(field, a), getField(field, b));
165
+ }
166
+ return (a, b) => {
167
+ for (let i = 0; i < fieldCount; i++) {
168
+ const result = multipliers[i] * cmp(getField(fields[i].field, a), getField(fields[i].field, b));
169
+ if (result) return result;
170
+ }
171
+ return 0;
172
+ };
173
+ }
174
+ prepareSearch(query, options) {
175
+ if (typeof query === "object") return query;
176
+ options = { ...options };
177
+ if (options.fields && !Array.isArray(options.fields)) options.fields = [options.fields];
178
+ if (options.sort && !Array.isArray(options.sort)) options.sort = [options.sort];
179
+ if (options.sort_empty && !Array.isArray(options.sort_empty))
180
+ options.sort_empty = [options.sort_empty];
181
+ return {
182
+ options,
183
+ query: String(query || "").toLowerCase(),
184
+ tokens: this.tokenize(query, options.respect_word_boundaries),
185
+ total: 0,
186
+ items: []
187
+ };
188
+ }
189
+ search(query, options) {
190
+ const search = this.prepareSearch(query, options);
191
+ const opts = search.options;
192
+ const q = search.query;
193
+ const fnScore = opts.score || this.getScoreFunction(search);
194
+ const items = this.items;
195
+ if (q.length) {
196
+ const iterate = Array.isArray(items) ? (cb) => items.forEach((item, i) => cb(item, i)) : (cb) => Object.keys(items).forEach((key) => cb(items[key], key));
197
+ iterate((item, id) => {
198
+ const score = fnScore(item);
199
+ if (opts.filter === false || score > 0) {
200
+ search.items.push({ score, id });
201
+ }
202
+ });
203
+ } else {
204
+ const iterate = Array.isArray(items) ? (cb) => items.forEach((item, i) => cb(item, i)) : (cb) => Object.keys(items).forEach((key) => cb(items[key], key));
205
+ iterate((item, id) => {
206
+ search.items.push({ score: 1, id });
207
+ });
208
+ }
209
+ const fnSort = this.getSortFunction(search, opts);
210
+ if (fnSort) search.items.sort(fnSort);
211
+ search.total = search.items.length;
212
+ if (typeof opts.limit === "number") {
213
+ search.items = search.items.slice(0, opts.limit);
214
+ }
215
+ return search;
216
+ }
217
+ }
218
+ function escapeHtml(str) {
219
+ if (typeof str !== "string") return "";
220
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
221
+ }
222
+ function debounce(fn, delay) {
223
+ let timer;
224
+ return function(...args) {
225
+ clearTimeout(timer);
226
+ timer = setTimeout(() => fn.apply(this, args), delay);
227
+ };
228
+ }
229
+ let idCounter = 0;
230
+ function uid(prefix = "selectize") {
231
+ return `${prefix}-${++idCounter}-${Math.random().toString(36).slice(2, 8)}`;
232
+ }
233
+ function hashKey(value) {
234
+ if (typeof value === "undefined" || value === null) return null;
235
+ return typeof value === "boolean" ? value ? "1" : "0" : String(value);
236
+ }
237
+ function highlight(text, search) {
238
+ if (!search || !search.length) return escapeHtml(text);
239
+ const escaped = escapeHtml(text);
240
+ const regex = new RegExp(
241
+ "(" + search.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1") + ")",
242
+ "ig"
243
+ );
244
+ return escaped.replace(regex, '<span class="font-semibold text-inherit">$1</span>');
245
+ }
246
+ let measureCanvas;
247
+ function measureString(str, el) {
248
+ if (!measureCanvas) {
249
+ measureCanvas = document.createElement("canvas");
250
+ }
251
+ const ctx = measureCanvas.getContext("2d");
252
+ if (el) {
253
+ const style = window.getComputedStyle(el);
254
+ ctx.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
255
+ }
256
+ return ctx.measureText(str).width;
257
+ }
258
+ function autoGrow(input, extraWidth = 10) {
259
+ const val = input.value || input.placeholder || "";
260
+ const width = measureString(val, input) + extraWidth;
261
+ input.style.width = Math.max(width, 60) + "px";
262
+ }
263
+ function isSelectElement(el) {
264
+ return el.tagName && el.tagName.toLowerCase() === "select";
265
+ }
266
+ function readSelectOptions(selectEl) {
267
+ const options = [];
268
+ const optgroups = [];
269
+ const selectedValues = [];
270
+ const processOption = (optionEl, optgroup = null) => {
271
+ const value = optionEl.value;
272
+ const text = optionEl.textContent.trim();
273
+ const disabled = optionEl.disabled;
274
+ if (!value && !text) return;
275
+ const data = { value, text, disabled };
276
+ if (optionEl.dataset.data) {
277
+ try {
278
+ Object.assign(data, JSON.parse(optionEl.dataset.data));
279
+ } catch (_) {
280
+ }
281
+ }
282
+ if (optgroup !== null) {
283
+ data.optgroup = optgroup;
284
+ }
285
+ if (optionEl.selected) {
286
+ selectedValues.push(value);
287
+ }
288
+ options.push(data);
289
+ };
290
+ for (const child of selectEl.children) {
291
+ if (child.tagName.toLowerCase() === "optgroup") {
292
+ const groupId = child.getAttribute("label") || "";
293
+ optgroups.push({
294
+ value: groupId,
295
+ label: groupId,
296
+ disabled: child.disabled
297
+ });
298
+ for (const opt of child.children) {
299
+ processOption(opt, groupId);
300
+ }
301
+ } else if (child.tagName.toLowerCase() === "option") {
302
+ processOption(child);
303
+ }
304
+ }
305
+ return { options, optgroups, selectedValues };
306
+ }
307
+ function isRtl(el) {
308
+ const style = window.getComputedStyle(el);
309
+ return style.direction === "rtl";
310
+ }
311
+ const DEFAULTS = {
312
+ delimiter: ",",
313
+ splitOn: null,
314
+ persist: true,
315
+ diacritics: true,
316
+ create: false,
317
+ showAddOptionOnCreate: true,
318
+ createOnBlur: false,
319
+ createFilter: null,
320
+ highlight: true,
321
+ openOnFocus: true,
322
+ maxOptions: 1e3,
323
+ maxItems: null,
324
+ hideSelected: null,
325
+ selectOnTab: true,
326
+ preload: false,
327
+ allowEmptyOption: false,
328
+ closeAfterSelect: false,
329
+ loadThrottle: 300,
330
+ loadingClass: "loading",
331
+ placeholder: "",
332
+ mode: null,
333
+ // 'single' | 'multi' — auto-detected
334
+ search: true,
335
+ showArrow: true,
336
+ valueField: "value",
337
+ labelField: "text",
338
+ disabledField: "disabled",
339
+ optgroupField: "optgroup",
340
+ optgroupLabelField: "label",
341
+ optgroupValueField: "value",
342
+ sortField: "$order",
343
+ searchField: ["text"],
344
+ searchConjunction: "and",
345
+ respectWordBoundaries: false,
346
+ normalize: true,
347
+ plugins: [],
348
+ // Render functions — return HTML strings
349
+ render: {
350
+ option: null,
351
+ item: null,
352
+ optionCreate: null,
353
+ optgroupHeader: null,
354
+ noResults: null,
355
+ loading: null
356
+ },
357
+ // Callbacks
358
+ load: null,
359
+ score: null,
360
+ onChange: null,
361
+ onItemAdd: null,
362
+ onItemRemove: null,
363
+ onClear: null,
364
+ onOptionAdd: null,
365
+ onOptionRemove: null,
366
+ onDropdownOpen: null,
367
+ onDropdownClose: null,
368
+ onType: null,
369
+ onFocus: null,
370
+ onBlur: null,
371
+ onInitialize: null
372
+ };
373
+ const pluginRegistry = {};
374
+ function createSelectizeComponent(userConfig = {}) {
375
+ return () => ({
376
+ // ── Reactive state ──────────────────────────────────────
377
+ isOpen: false,
378
+ isFocused: false,
379
+ isDisabled: false,
380
+ isLocked: false,
381
+ isLoading: false,
382
+ isInvalid: false,
383
+ query: "",
384
+ activeIndex: -1,
385
+ caretPos: 0,
386
+ items: [],
387
+ // selected values (strings)
388
+ options: {},
389
+ // { [value]: { value, text, ... } }
390
+ optgroups: {},
391
+ // { [id]: { value, label, ... } }
392
+ userOptions: {},
393
+ // user-created option values
394
+ optionOrder: [],
395
+ // maintains insertion order
396
+ loadedSearches: {},
397
+ lastQuery: "",
398
+ // Internal
399
+ _config: {},
400
+ _sifter: null,
401
+ _sourceEl: null,
402
+ _id: "",
403
+ _rtl: false,
404
+ _plugins: [],
405
+ _renderCache: {},
406
+ // ── Computed properties ─────────────────────────────────
407
+ get config() {
408
+ return this._config;
409
+ },
410
+ get isMultiple() {
411
+ return this._config.mode === "multi";
412
+ },
413
+ get isSingle() {
414
+ return this._config.mode === "single";
415
+ },
416
+ get isFull() {
417
+ return this._config.maxItems !== null && this.items.length >= this._config.maxItems;
418
+ },
419
+ get hasOptions() {
420
+ return Object.keys(this.options).length > 0;
421
+ },
422
+ get canCreate() {
423
+ if (!this._config.create) return false;
424
+ if (!this.query.trim()) return false;
425
+ if (this.isFull) return false;
426
+ if (this._config.createFilter) {
427
+ const filter = this._config.createFilter;
428
+ if (typeof filter === "function") return filter(this.query);
429
+ if (filter instanceof RegExp) return filter.test(this.query);
430
+ if (typeof filter === "string") return new RegExp(filter).test(this.query);
431
+ }
432
+ const existing = Object.values(this.options).find(
433
+ (o) => {
434
+ var _a;
435
+ return ((_a = o[this._config.labelField]) == null ? void 0 : _a.toLowerCase()) === this.query.toLowerCase();
436
+ }
437
+ );
438
+ return !existing;
439
+ },
440
+ get selectedItems() {
441
+ return this.items.map((val) => this.options[hashKey(val)]).filter(Boolean);
442
+ },
443
+ get filteredOptions() {
444
+ return this._getFilteredOptions();
445
+ },
446
+ get placeholderText() {
447
+ if (this.items.length > 0 && this.isSingle) return "";
448
+ return this._config.placeholder || "";
449
+ },
450
+ get currentValueText() {
451
+ if (!this.isSingle || !this.items.length) return "";
452
+ const opt = this.options[hashKey(this.items[0])];
453
+ return opt ? opt[this._config.labelField] : "";
454
+ },
455
+ // ── Lifecycle ───────────────────────────────────────────
456
+ init() {
457
+ this._id = uid();
458
+ this._config = { ...DEFAULTS, ...userConfig };
459
+ this._sourceEl = this.$el.querySelector('select, input[type="text"], input[type="hidden"]');
460
+ if (this._sourceEl && isSelectElement(this._sourceEl)) {
461
+ const parsed = readSelectOptions(this._sourceEl);
462
+ const configOptions = this._config.options || [];
463
+ const allOptions = [...parsed.options, ...configOptions];
464
+ this._registerOptions(allOptions);
465
+ for (const og of parsed.optgroups) {
466
+ this.optgroups[og.value] = og;
467
+ }
468
+ if (parsed.selectedValues.length) {
469
+ this.items = [...parsed.selectedValues];
470
+ }
471
+ if (!userConfig.mode) {
472
+ this._config.mode = this._sourceEl.multiple ? "multi" : "single";
473
+ }
474
+ if (this._sourceEl.hasAttribute("required")) this.isInvalid = !this.items.length;
475
+ if (this._sourceEl.disabled) this.isDisabled = true;
476
+ if (this._sourceEl.placeholder) this._config.placeholder = this._sourceEl.placeholder;
477
+ this._sourceEl.style.display = "none";
478
+ this._sourceEl.setAttribute("tabindex", "-1");
479
+ this._rtl = isRtl(this._sourceEl);
480
+ } else {
481
+ const configOptions = this._config.options || [];
482
+ this._registerOptions(configOptions);
483
+ if (this._config.optgroups) {
484
+ for (const og of this._config.optgroups) {
485
+ this.optgroups[og[this._config.optgroupValueField]] = og;
486
+ }
487
+ }
488
+ if (this._config.items) {
489
+ this.items = [...this._config.items];
490
+ }
491
+ }
492
+ if (!this._config.mode) {
493
+ this._config.mode = this._config.maxItems === 1 ? "single" : "multi";
494
+ }
495
+ if (this._config.mode === "single") {
496
+ this._config.maxItems = 1;
497
+ }
498
+ if (this._config.hideSelected === null) {
499
+ this._config.hideSelected = this._config.mode === "multi";
500
+ }
501
+ this._sifter = new Sifter(this.options, { diacritics: this._config.diacritics });
502
+ this._initPlugins();
503
+ if (this._config.load && this._config.loadThrottle) {
504
+ this._debouncedLoad = debounce(this._performLoad.bind(this), this._config.loadThrottle);
505
+ }
506
+ if (this._config.preload) {
507
+ this.$nextTick(() => {
508
+ if (this._config.preload === "focus") ;
509
+ else {
510
+ this._performLoad("");
511
+ }
512
+ });
513
+ }
514
+ this._trigger("onInitialize");
515
+ this._onClickOutside = (e) => {
516
+ if (!this.$el.contains(e.target)) {
517
+ this.close();
518
+ this.blur();
519
+ }
520
+ };
521
+ document.addEventListener("mousedown", this._onClickOutside);
522
+ },
523
+ destroy() {
524
+ document.removeEventListener("mousedown", this._onClickOutside);
525
+ if (this._sourceEl) {
526
+ this._sourceEl.style.display = "";
527
+ this._sourceEl.removeAttribute("tabindex");
528
+ }
529
+ },
530
+ // ── Plugin System ───────────────────────────────────────
531
+ _initPlugins() {
532
+ const plugins = this._config.plugins || [];
533
+ for (const plugin of plugins) {
534
+ const name = typeof plugin === "string" ? plugin : plugin.name;
535
+ const opts = typeof plugin === "string" ? {} : plugin.options || {};
536
+ if (pluginRegistry[name]) {
537
+ pluginRegistry[name].call(this, opts);
538
+ this._plugins.push(name);
539
+ } else {
540
+ console.warn(`[selectize] Plugin "${name}" not found.`);
541
+ }
542
+ }
543
+ },
544
+ // ── Option Management ───────────────────────────────────
545
+ _registerOptions(optionsList) {
546
+ for (const opt of optionsList) {
547
+ this.addOption(opt, true);
548
+ }
549
+ },
550
+ addOption(data, silent = false) {
551
+ if (Array.isArray(data)) {
552
+ for (const item of data) this.addOption(item, silent);
553
+ return;
554
+ }
555
+ const key = hashKey(data[this._config.valueField]);
556
+ if (key === null || this.options[key]) return;
557
+ data.$order = data.$order || ++this._orderCounter || (this._orderCounter = 1);
558
+ this.options[key] = data;
559
+ this.optionOrder.push(key);
560
+ if (this._sifter) this._sifter.items = this.options;
561
+ this._clearRenderCache();
562
+ if (!silent) this._trigger("onOptionAdd", key, data);
563
+ },
564
+ updateOption(value, data) {
565
+ const key = hashKey(value);
566
+ if (!key || !this.options[key]) return;
567
+ const newKey = hashKey(data[this._config.valueField]);
568
+ data.$order = this.options[key].$order;
569
+ this.options[newKey] = data;
570
+ if (key !== newKey) {
571
+ delete this.options[key];
572
+ const idx = this.optionOrder.indexOf(key);
573
+ if (idx !== -1) this.optionOrder[idx] = newKey;
574
+ const itemIdx = this.items.indexOf(key);
575
+ if (itemIdx !== -1) this.items[itemIdx] = newKey;
576
+ }
577
+ if (this._sifter) this._sifter.items = this.options;
578
+ this._clearRenderCache();
579
+ },
580
+ removeOption(value) {
581
+ const key = hashKey(value);
582
+ if (!key) return;
583
+ delete this.options[key];
584
+ delete this.userOptions[key];
585
+ const idx = this.optionOrder.indexOf(key);
586
+ if (idx !== -1) this.optionOrder.splice(idx, 1);
587
+ this.items = this.items.filter((v) => v !== key);
588
+ if (this._sifter) this._sifter.items = this.options;
589
+ this._clearRenderCache();
590
+ this._trigger("onOptionRemove", key);
591
+ },
592
+ clearOptions() {
593
+ const keep = {};
594
+ for (const val of this.items) {
595
+ if (this.options[val]) keep[val] = this.options[val];
596
+ }
597
+ this.options = keep;
598
+ this.optionOrder = Object.keys(keep);
599
+ this.userOptions = {};
600
+ if (this._sifter) this._sifter.items = this.options;
601
+ this._clearRenderCache();
602
+ },
603
+ getOption(value) {
604
+ return this.options[hashKey(value)] || null;
605
+ },
606
+ // ── Item (Selection) Management ─────────────────────────
607
+ addItem(value, silent = false) {
608
+ const key = hashKey(value);
609
+ if (!key || !this.options[key]) return;
610
+ if (this.items.includes(key)) return;
611
+ if (this.isFull) return;
612
+ if (this.isSingle && this.items.length) {
613
+ this.removeItem(this.items[0], true);
614
+ }
615
+ this.items.push(key);
616
+ this.caretPos = this.items.length;
617
+ this._syncSourceElement();
618
+ this._clearRenderCache();
619
+ this.query = "";
620
+ if (this._config.closeAfterSelect || this.isSingle) {
621
+ this.close();
622
+ }
623
+ if (this.isFull) {
624
+ this.close();
625
+ }
626
+ if (!silent) {
627
+ this._trigger("onItemAdd", key, this.options[key]);
628
+ this._trigger("onChange", this.getValue());
629
+ }
630
+ },
631
+ removeItem(value, silent = false) {
632
+ const key = hashKey(value);
633
+ const idx = this.items.indexOf(key);
634
+ if (idx === -1) return;
635
+ this.items.splice(idx, 1);
636
+ if (this.caretPos > this.items.length) {
637
+ this.caretPos = this.items.length;
638
+ }
639
+ this._syncSourceElement();
640
+ this._clearRenderCache();
641
+ if (!silent) {
642
+ this._trigger("onItemRemove", key);
643
+ this._trigger("onChange", this.getValue());
644
+ }
645
+ },
646
+ clear(silent = false) {
647
+ if (!this.items.length) return;
648
+ this.items = [];
649
+ this.caretPos = 0;
650
+ this._syncSourceElement();
651
+ this._clearRenderCache();
652
+ if (!silent) {
653
+ this._trigger("onClear");
654
+ this._trigger("onChange", this.getValue());
655
+ }
656
+ },
657
+ getValue() {
658
+ if (this.isSingle) {
659
+ return this.items.length ? this.items[0] : "";
660
+ }
661
+ return [...this.items];
662
+ },
663
+ setValue(value, silent = false) {
664
+ this.clear(true);
665
+ const values = Array.isArray(value) ? value : [value];
666
+ for (const v of values) {
667
+ if (v !== "" && v !== null && v !== void 0) {
668
+ this.addItem(v, true);
669
+ }
670
+ }
671
+ if (!silent) {
672
+ this._trigger("onChange", this.getValue());
673
+ }
674
+ },
675
+ // ── Create Item ─────────────────────────────────────────
676
+ createItem(input = null) {
677
+ const val = input !== null ? input : this.query;
678
+ if (!val.trim()) return;
679
+ if (!this._config.create) return;
680
+ const createFn = this._config.create;
681
+ let data;
682
+ if (typeof createFn === "function") {
683
+ data = createFn(val, (result) => {
684
+ if (result) {
685
+ this.addOption(result);
686
+ this.addItem(result[this._config.valueField]);
687
+ }
688
+ });
689
+ if (data && typeof data === "object") {
690
+ this.addOption(data);
691
+ this.addItem(data[this._config.valueField]);
692
+ }
693
+ } else {
694
+ data = {};
695
+ data[this._config.valueField] = val;
696
+ data[this._config.labelField] = val;
697
+ this.addOption(data);
698
+ this.addItem(val);
699
+ }
700
+ this.query = "";
701
+ this._clearRenderCache();
702
+ },
703
+ // ── Search ──────────────────────────────────────────────
704
+ _getFilteredOptions() {
705
+ const config = this._config;
706
+ if (!this._sifter) return [];
707
+ const searchFields = Array.isArray(config.searchField) ? config.searchField : [config.searchField];
708
+ let sort;
709
+ if (config.sortField) {
710
+ if (typeof config.sortField === "string") {
711
+ sort = [{ field: config.sortField, direction: "asc" }];
712
+ } else if (Array.isArray(config.sortField)) {
713
+ sort = config.sortField;
714
+ } else {
715
+ sort = [config.sortField];
716
+ }
717
+ } else {
718
+ sort = [{ field: "$order", direction: "asc" }];
719
+ }
720
+ let q = this.query;
721
+ if (config.normalize && q) {
722
+ q = q.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
723
+ }
724
+ const searchOptions = {
725
+ fields: searchFields,
726
+ conjunction: config.searchConjunction,
727
+ sort,
728
+ nesting: searchFields.some((f) => f.includes(".")),
729
+ respect_word_boundaries: config.respectWordBoundaries,
730
+ limit: config.maxOptions
731
+ };
732
+ if (config.score) {
733
+ searchOptions.score = config.score;
734
+ }
735
+ const results = this._sifter.search(q, searchOptions);
736
+ let filtered = results.items.map((item) => {
737
+ const opt = this.options[item.id];
738
+ return opt ? { ...opt, _score: item.score } : null;
739
+ }).filter(Boolean);
740
+ if (config.hideSelected) {
741
+ filtered = filtered.filter(
742
+ (opt) => !this.items.includes(hashKey(opt[config.valueField]))
743
+ );
744
+ }
745
+ filtered = filtered.filter((opt) => !opt[config.disabledField]);
746
+ return filtered;
747
+ },
748
+ // ── Dropdown Control ────────────────────────────────────
749
+ open() {
750
+ if (this.isOpen || this.isDisabled || this.isLocked) return;
751
+ this.isOpen = true;
752
+ this.activeIndex = this._config.setFirstOptionActive ? 0 : -1;
753
+ this._trigger("onDropdownOpen");
754
+ this.$nextTick(() => {
755
+ this._scrollToActive();
756
+ });
757
+ },
758
+ close() {
759
+ if (!this.isOpen) return;
760
+ this.isOpen = false;
761
+ this.activeIndex = -1;
762
+ this._trigger("onDropdownClose");
763
+ },
764
+ toggle() {
765
+ this.isOpen ? this.close() : this.open();
766
+ },
767
+ // ── Focus / Blur ────────────────────────────────────────
768
+ focus() {
769
+ if (this.isDisabled) return;
770
+ this.isFocused = true;
771
+ const input = this.$refs.searchInput;
772
+ if (input) {
773
+ this.$nextTick(() => input.focus());
774
+ }
775
+ if (this._config.openOnFocus) {
776
+ this.open();
777
+ }
778
+ if (this._config.preload === "focus" && !this.loadedSearches[""]) {
779
+ this._performLoad("");
780
+ }
781
+ this._trigger("onFocus");
782
+ },
783
+ blur() {
784
+ if (!this.isFocused) return;
785
+ this.isFocused = false;
786
+ if (this._config.createOnBlur && this.query.trim() && this.canCreate) {
787
+ this.createItem();
788
+ }
789
+ this.close();
790
+ this._trigger("onBlur");
791
+ },
792
+ // ── Keyboard Navigation ─────────────────────────────────
793
+ onKeyDown(e) {
794
+ if (this.isDisabled || this.isLocked) return;
795
+ const opts = this.filteredOptions;
796
+ const canCreateNow = this.canCreate;
797
+ const totalItems = opts.length + (canCreateNow ? 1 : 0);
798
+ switch (e.key) {
799
+ case "ArrowDown":
800
+ e.preventDefault();
801
+ if (!this.isOpen) {
802
+ this.open();
803
+ } else {
804
+ this.activeIndex = Math.min(this.activeIndex + 1, totalItems - 1);
805
+ this._scrollToActive();
806
+ }
807
+ break;
808
+ case "ArrowUp":
809
+ e.preventDefault();
810
+ if (this.isOpen) {
811
+ this.activeIndex = Math.max(this.activeIndex - 1, 0);
812
+ this._scrollToActive();
813
+ }
814
+ break;
815
+ case "Enter":
816
+ e.preventDefault();
817
+ if (this.isOpen && this.activeIndex >= 0) {
818
+ if (this.activeIndex < opts.length) {
819
+ this.selectOption(opts[this.activeIndex]);
820
+ } else if (canCreateNow) {
821
+ this.createItem();
822
+ }
823
+ } else if (!this.isOpen) {
824
+ this.open();
825
+ }
826
+ break;
827
+ case "Escape":
828
+ e.preventDefault();
829
+ this.close();
830
+ break;
831
+ case "Tab":
832
+ if (this.isOpen && this._config.selectOnTab && this.activeIndex >= 0) {
833
+ e.preventDefault();
834
+ if (this.activeIndex < opts.length) {
835
+ this.selectOption(opts[this.activeIndex]);
836
+ } else if (canCreateNow) {
837
+ this.createItem();
838
+ }
839
+ }
840
+ break;
841
+ case "Backspace":
842
+ if (!this.query && this.items.length && this.isMultiple) {
843
+ e.preventDefault();
844
+ const lastItem = this.items[this.items.length - 1];
845
+ this.removeItem(lastItem);
846
+ }
847
+ break;
848
+ case "Delete":
849
+ if (!this.query && this.items.length && this.isMultiple) {
850
+ e.preventDefault();
851
+ const lastItem = this.items[this.items.length - 1];
852
+ this.removeItem(lastItem);
853
+ }
854
+ break;
855
+ case "a":
856
+ case "A":
857
+ if ((e.ctrlKey || e.metaKey) && this.isMultiple) {
858
+ e.preventDefault();
859
+ }
860
+ break;
861
+ }
862
+ },
863
+ // ── Input Handling ──────────────────────────────────────
864
+ onInput() {
865
+ this._trigger("onType", this.query);
866
+ if (!this.isOpen) {
867
+ this.open();
868
+ }
869
+ this.activeIndex = this._config.setFirstOptionActive || this.query ? 0 : -1;
870
+ if (this._config.load && this.query) {
871
+ const q = this.query;
872
+ if (!this.loadedSearches[q]) {
873
+ if (this._debouncedLoad) {
874
+ this._debouncedLoad(q);
875
+ } else {
876
+ this._performLoad(q);
877
+ }
878
+ }
879
+ }
880
+ if (this.$refs.searchInput && this.isMultiple) {
881
+ autoGrow(this.$refs.searchInput);
882
+ }
883
+ },
884
+ onPaste(e) {
885
+ if (!this.isMultiple) return;
886
+ const paste = (e.clipboardData || window.clipboardData).getData("text");
887
+ if (!paste) return;
888
+ const splitOn = this._config.splitOn || this._config.delimiter;
889
+ if (!splitOn) return;
890
+ e.preventDefault();
891
+ const regex = splitOn instanceof RegExp ? splitOn : new RegExp("[" + splitOn.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "]");
892
+ const parts = paste.split(regex).map((s) => s.trim()).filter(Boolean);
893
+ for (const part of parts) {
894
+ const match = Object.values(this.options).find(
895
+ (o) => {
896
+ var _a, _b;
897
+ return ((_a = o[this._config.labelField]) == null ? void 0 : _a.toLowerCase()) === part.toLowerCase() || ((_b = o[this._config.valueField]) == null ? void 0 : _b.toLowerCase()) === part.toLowerCase();
898
+ }
899
+ );
900
+ if (match) {
901
+ this.addItem(match[this._config.valueField]);
902
+ } else if (this._config.create) {
903
+ this.createItem(part);
904
+ }
905
+ }
906
+ },
907
+ // ── Option Selection ────────────────────────────────────
908
+ selectOption(option) {
909
+ if (!option) return;
910
+ if (option[this._config.disabledField]) return;
911
+ const value = option[this._config.valueField];
912
+ this.addItem(value);
913
+ this.query = "";
914
+ if (this.$refs.searchInput) {
915
+ this.$refs.searchInput.focus();
916
+ if (this.isMultiple) autoGrow(this.$refs.searchInput);
917
+ }
918
+ },
919
+ // ── Remote Loading ──────────────────────────────────────
920
+ _performLoad(query) {
921
+ if (!this._config.load) return;
922
+ if (this.loadedSearches[query]) return;
923
+ this.isLoading = true;
924
+ this.loadedSearches[query] = true;
925
+ this._config.load(query, (results) => {
926
+ this.isLoading = false;
927
+ if (results && Array.isArray(results)) {
928
+ for (const item of results) {
929
+ this.addOption(item, true);
930
+ }
931
+ this._clearRenderCache();
932
+ }
933
+ });
934
+ },
935
+ // ── Rendering Helpers ───────────────────────────────────
936
+ renderOption(option) {
937
+ var _a;
938
+ const config = this._config;
939
+ const label = option[config.labelField] || "";
940
+ if ((_a = config.render) == null ? void 0 : _a.option) {
941
+ return config.render.option(option, escapeHtml);
942
+ }
943
+ if (config.highlight && this.query) {
944
+ return highlight(label, this.query);
945
+ }
946
+ return escapeHtml(label);
947
+ },
948
+ renderItem(option) {
949
+ var _a;
950
+ const config = this._config;
951
+ const label = option[config.labelField] || "";
952
+ if ((_a = config.render) == null ? void 0 : _a.item) {
953
+ return config.render.item(option, escapeHtml);
954
+ }
955
+ return escapeHtml(label);
956
+ },
957
+ renderOptionCreate() {
958
+ var _a;
959
+ const config = this._config;
960
+ if ((_a = config.render) == null ? void 0 : _a.optionCreate) {
961
+ return config.render.optionCreate({ input: this.query }, escapeHtml);
962
+ }
963
+ return `Add <span class="font-medium">${escapeHtml(this.query)}</span>...`;
964
+ },
965
+ renderNoResults() {
966
+ var _a;
967
+ const config = this._config;
968
+ if ((_a = config.render) == null ? void 0 : _a.noResults) {
969
+ return config.render.noResults({ query: this.query }, escapeHtml);
970
+ }
971
+ return "No results found";
972
+ },
973
+ renderLoading() {
974
+ var _a;
975
+ const config = this._config;
976
+ if ((_a = config.render) == null ? void 0 : _a.loading) {
977
+ return config.render.loading({ query: this.query }, escapeHtml);
978
+ }
979
+ return "Loading...";
980
+ },
981
+ // ── Optgroup Support ────────────────────────────────────
982
+ addOptionGroup(id, data) {
983
+ this.optgroups[id] = data;
984
+ this._clearRenderCache();
985
+ },
986
+ removeOptionGroup(id) {
987
+ delete this.optgroups[id];
988
+ this._clearRenderCache();
989
+ },
990
+ getGroupedOptions() {
991
+ const options = this.filteredOptions;
992
+ const config = this._config;
993
+ const groups = {};
994
+ const ungrouped = [];
995
+ for (const opt of options) {
996
+ const groupId = opt[config.optgroupField];
997
+ if (groupId && this.optgroups[groupId]) {
998
+ if (!groups[groupId]) groups[groupId] = [];
999
+ groups[groupId].push(opt);
1000
+ } else {
1001
+ ungrouped.push(opt);
1002
+ }
1003
+ }
1004
+ const result = [];
1005
+ for (const [id, items] of Object.entries(groups)) {
1006
+ const group = this.optgroups[id];
1007
+ result.push({
1008
+ id,
1009
+ label: group[config.optgroupLabelField] || id,
1010
+ options: items
1011
+ });
1012
+ }
1013
+ if (ungrouped.length) {
1014
+ result.push({ id: null, label: null, options: ungrouped });
1015
+ }
1016
+ return result;
1017
+ },
1018
+ get hasOptgroups() {
1019
+ return Object.keys(this.optgroups).length > 0;
1020
+ },
1021
+ // ── State Control ───────────────────────────────────────
1022
+ lock() {
1023
+ this.isLocked = true;
1024
+ this.close();
1025
+ },
1026
+ unlock() {
1027
+ this.isLocked = false;
1028
+ },
1029
+ disable() {
1030
+ this.isDisabled = true;
1031
+ this.close();
1032
+ },
1033
+ enable() {
1034
+ this.isDisabled = false;
1035
+ },
1036
+ setMaxItems(max) {
1037
+ this._config.maxItems = max;
1038
+ if (this.isFull) this.close();
1039
+ },
1040
+ // ── Source Element Sync ─────────────────────────────────
1041
+ _syncSourceElement() {
1042
+ var _a;
1043
+ if (!this._sourceEl) return;
1044
+ if (isSelectElement(this._sourceEl)) {
1045
+ for (const opt of this._sourceEl.options) {
1046
+ opt.selected = this.items.includes(opt.value);
1047
+ }
1048
+ for (const val of this.items) {
1049
+ const exists = Array.from(this._sourceEl.options).some((o) => o.value === val);
1050
+ if (!exists) {
1051
+ const optEl = document.createElement("option");
1052
+ optEl.value = val;
1053
+ optEl.textContent = ((_a = this.options[val]) == null ? void 0 : _a[this._config.labelField]) || val;
1054
+ optEl.selected = true;
1055
+ this._sourceEl.appendChild(optEl);
1056
+ }
1057
+ }
1058
+ this._sourceEl.dispatchEvent(new Event("change", { bubbles: true }));
1059
+ } else {
1060
+ this._sourceEl.value = this.isSingle ? this.items[0] || "" : this.items.join(this._config.delimiter);
1061
+ this._sourceEl.dispatchEvent(new Event("input", { bubbles: true }));
1062
+ this._sourceEl.dispatchEvent(new Event("change", { bubbles: true }));
1063
+ }
1064
+ },
1065
+ // ── Scroll Management ───────────────────────────────────
1066
+ _scrollToActive() {
1067
+ this.$nextTick(() => {
1068
+ const dropdown = this.$refs.dropdown;
1069
+ if (!dropdown) return;
1070
+ const active = dropdown.querySelector('[data-active="true"]');
1071
+ if (active) {
1072
+ active.scrollIntoView({ block: "nearest" });
1073
+ }
1074
+ });
1075
+ },
1076
+ // ── Event Triggering ────────────────────────────────────
1077
+ _trigger(callbackName, ...args) {
1078
+ const cb = this._config[callbackName];
1079
+ if (typeof cb === "function") {
1080
+ cb.apply(this, args);
1081
+ }
1082
+ const eventName = callbackName.replace(/^on/, "").toLowerCase();
1083
+ this.$el.dispatchEvent(
1084
+ new CustomEvent(`selectra:${eventName}`, {
1085
+ detail: args,
1086
+ bubbles: true
1087
+ })
1088
+ );
1089
+ },
1090
+ // ── Cache ───────────────────────────────────────────────
1091
+ _clearRenderCache() {
1092
+ this._renderCache = {};
1093
+ },
1094
+ // ── Helper: Check if an option is selected ──────────────
1095
+ isSelected(option) {
1096
+ return this.items.includes(hashKey(option[this._config.valueField]));
1097
+ },
1098
+ // ── Helper: Option key for x-for ────────────────────────
1099
+ optionKey(option) {
1100
+ return hashKey(option[this._config.valueField]);
1101
+ }
1102
+ });
1103
+ }
1104
+ function registerPlugin(name, fn) {
1105
+ pluginRegistry[name] = fn;
1106
+ }
1107
+ function getDefaults() {
1108
+ return { ...DEFAULTS };
1109
+ }
1110
+ registerPlugin("remove_button", function(options = {}) {
1111
+ var _a;
1112
+ const {
1113
+ label = "&times;",
1114
+ title = "Remove",
1115
+ className = ""
1116
+ } = options;
1117
+ const originalRenderItem = (_a = this._config.render) == null ? void 0 : _a.item;
1118
+ if (!this._config.render) this._config.render = {};
1119
+ const self = this;
1120
+ this._config.render.item = function(data, escape) {
1121
+ const labelField = self._config.labelField;
1122
+ self._config.valueField;
1123
+ const text = originalRenderItem ? originalRenderItem(data, escape) : escape(data[labelField] || "");
1124
+ return `<span class="inline-flex items-center">${text}</span>`;
1125
+ };
1126
+ this._showRemoveButton = true;
1127
+ this._removeButtonLabel = label;
1128
+ this._removeButtonTitle = title;
1129
+ this._removeButtonClass = className;
1130
+ });
1131
+ registerPlugin("clear_button", function(options = {}) {
1132
+ const {
1133
+ title = "Clear All",
1134
+ className = "",
1135
+ label = "&times;"
1136
+ } = options;
1137
+ this._showClearButton = true;
1138
+ this._clearButtonTitle = title;
1139
+ this._clearButtonLabel = label;
1140
+ this._clearButtonClass = className;
1141
+ });
1142
+ registerPlugin("restore_on_backspace", function(options = {}) {
1143
+ const textFn = options.text || ((opt) => opt[this._config.labelField] || "");
1144
+ const originalOnKeyDown = this.onKeyDown.bind(this);
1145
+ this.onKeyDown = (e) => {
1146
+ if (e.key === "Backspace" && !this.query && this.items.length && this.isMultiple) {
1147
+ e.preventDefault();
1148
+ const lastValue = this.items[this.items.length - 1];
1149
+ const lastOption = this.options[lastValue];
1150
+ this.removeItem(lastValue);
1151
+ if (lastOption) {
1152
+ this.query = textFn(lastOption);
1153
+ if (this.$refs.searchInput) {
1154
+ this.$refs.searchInput.value = this.query;
1155
+ }
1156
+ }
1157
+ return;
1158
+ }
1159
+ originalOnKeyDown(e);
1160
+ };
1161
+ });
1162
+ registerPlugin("dropdown_header", function(options = {}) {
1163
+ const {
1164
+ title = "",
1165
+ showClose = true,
1166
+ headerClass = ""
1167
+ } = options;
1168
+ this._dropdownHeader = true;
1169
+ this._dropdownHeaderTitle = title;
1170
+ this._dropdownHeaderShowClose = showClose;
1171
+ this._dropdownHeaderClass = headerClass;
1172
+ });
1173
+ registerPlugin("tag_limit", function(options = {}) {
1174
+ const { tagLimit = 3 } = options;
1175
+ this._tagLimit = tagLimit;
1176
+ this._showAllTags = false;
1177
+ Object.defineProperty(this, "visibleItems", {
1178
+ get() {
1179
+ const all = this.selectedItems;
1180
+ if (this.isFocused || this._showAllTags || !this._tagLimit) return all;
1181
+ return all.slice(0, this._tagLimit);
1182
+ }
1183
+ });
1184
+ Object.defineProperty(this, "hiddenItemCount", {
1185
+ get() {
1186
+ const all = this.selectedItems;
1187
+ if (this.isFocused || this._showAllTags || !this._tagLimit) return 0;
1188
+ return Math.max(0, all.length - this._tagLimit);
1189
+ }
1190
+ });
1191
+ });
1192
+ registerPlugin("auto_select_on_type", function() {
1193
+ const originalBlur = this.blur.bind(this);
1194
+ this.blur = () => {
1195
+ if (this.query.trim() && this.filteredOptions.length) {
1196
+ const first = this.filteredOptions[0];
1197
+ this.selectOption(first);
1198
+ }
1199
+ this.query = "";
1200
+ originalBlur();
1201
+ };
1202
+ });
1203
+ registerPlugin("select_on_focus", function() {
1204
+ const originalFocus = this.focus.bind(this);
1205
+ this.focus = () => {
1206
+ originalFocus();
1207
+ if (this.isSingle && this.items.length) {
1208
+ const current = this.options[this.items[0]];
1209
+ if (current) {
1210
+ this.query = current[this._config.labelField] || "";
1211
+ if (this.$refs.searchInput) {
1212
+ this.$nextTick(() => this.$refs.searchInput.select());
1213
+ }
1214
+ }
1215
+ }
1216
+ };
1217
+ });
1218
+ registerPlugin("read_only", function(options = {}) {
1219
+ const { readOnly = true } = options;
1220
+ this._isReadOnly = readOnly;
1221
+ this.readonly = (value) => {
1222
+ this._isReadOnly = value !== void 0 ? value : !this._isReadOnly;
1223
+ if (this._isReadOnly) {
1224
+ this.close();
1225
+ }
1226
+ };
1227
+ const originalAddItem = this.addItem.bind(this);
1228
+ const originalRemoveItem = this.removeItem.bind(this);
1229
+ const originalCreateItem = this.createItem.bind(this);
1230
+ const originalClear = this.clear.bind(this);
1231
+ this.addItem = (value, silent) => {
1232
+ if (this._isReadOnly) return;
1233
+ originalAddItem(value, silent);
1234
+ };
1235
+ this.removeItem = (value, silent) => {
1236
+ if (this._isReadOnly) return;
1237
+ originalRemoveItem(value, silent);
1238
+ };
1239
+ this.createItem = (input) => {
1240
+ if (this._isReadOnly) return;
1241
+ originalCreateItem(input);
1242
+ };
1243
+ this.clear = (silent) => {
1244
+ if (this._isReadOnly) return;
1245
+ originalClear(silent);
1246
+ };
1247
+ });
1248
+ registerPlugin("auto_position", function() {
1249
+ this._autoPosition = true;
1250
+ const originalOpen = this.open.bind(this);
1251
+ this.open = () => {
1252
+ originalOpen();
1253
+ this.$nextTick(() => {
1254
+ const dropdown = this.$refs.dropdown;
1255
+ const wrapper = this.$el;
1256
+ if (!dropdown || !wrapper) return;
1257
+ const wrapperRect = wrapper.getBoundingClientRect();
1258
+ const spaceBelow = window.innerHeight - wrapperRect.bottom;
1259
+ const spaceAbove = wrapperRect.top;
1260
+ const dropdownHeight = dropdown.offsetHeight || 250;
1261
+ if (spaceBelow < dropdownHeight && spaceAbove > spaceBelow) {
1262
+ this._dropdownPosition = "top";
1263
+ } else {
1264
+ this._dropdownPosition = "bottom";
1265
+ }
1266
+ });
1267
+ };
1268
+ this._dropdownPosition = "bottom";
1269
+ });
1270
+ /**
1271
+ * Selectra - Alpine.js Plugin
1272
+ *
1273
+ * A powerful, extensible <select> UI control for tagging, contact lists,
1274
+ * country selectors, and autocomplete. Built with Alpine.js and Tailwind CSS.
1275
+ *
1276
+ * @license Apache-2.0
1277
+ *
1278
+ * Usage:
1279
+ *
1280
+ * // Register the plugin
1281
+ * import Alpine from 'alpinejs';
1282
+ * import Selectra from 'selectra';
1283
+ * Alpine.plugin(Selectra);
1284
+ * Alpine.start();
1285
+ *
1286
+ * // In HTML (directive approach):
1287
+ * <div x-data="selectra({ options: [...], create: true })">
1288
+ * <template x-selectra></template>
1289
+ * </div>
1290
+ *
1291
+ * // Or initialize on existing <select>:
1292
+ * <div x-data="selectra()" x-selectra>
1293
+ * <select>
1294
+ * <option value="1">One</option>
1295
+ * <option value="2">Two</option>
1296
+ * </select>
1297
+ * </div>
1298
+ */
1299
+ function SelectraPlugin(Alpine) {
1300
+ Alpine.data("selectra", (config = {}) => {
1301
+ const componentFactory = createSelectizeComponent(config);
1302
+ return componentFactory();
1303
+ });
1304
+ Alpine.directive("selectra", (el, { expression }, { evaluate, cleanup }) => {
1305
+ });
1306
+ }
1307
+ SelectraPlugin.version = "1.0.0";
1308
+ export {
1309
+ DEFAULTS,
1310
+ Sifter,
1311
+ createSelectizeComponent,
1312
+ SelectraPlugin as default,
1313
+ escapeHtml,
1314
+ getDefaults,
1315
+ hashKey,
1316
+ registerPlugin
1317
+ };
1318
+ //# sourceMappingURL=selectra.es.js.map