@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,346 @@
1
+ # Selectra
2
+
3
+ > A powerful, extensible `<select>` UI control — rebuilt with **Alpine.js** and **Tailwind CSS**.
4
+
5
+ Selectra is a modern rewrite of [Selectize.js](https://github.com/selectize/selectize.js), designed for tagging, contact lists, country selectors, and autocomplete. It drops the jQuery dependency entirely in favor of Alpine.js reactivity and Tailwind CSS styling.
6
+
7
+ ---
8
+
9
+ ## Why Selectra?
10
+
11
+ | | Selectize.js (legacy) | **Selectra** |
12
+ |---|---|---|
13
+ | **UI Framework** | jQuery | Alpine.js |
14
+ | **Styling** | LESS / SCSS + Bootstrap themes | Tailwind CSS utilities |
15
+ | **Build Tool** | Gulp | Vite |
16
+ | **Tests** | Karma + Mocha | Vitest |
17
+ | **Bundle (JS)** | ~88 KB min | ~25 KB min |
18
+ | **CSS** | Multiple theme files | Single 21 KB utility file |
19
+ | **jQuery Required** | Yes | No |
20
+
21
+ ---
22
+
23
+ ## Features
24
+
25
+ - **Single & Multi Select** — auto-detected from `maxItems` or explicit `mode`
26
+ - **Tagging / Create** — create new options on the fly with validation
27
+ - **Fuzzy Search** — built-in Sifter engine with diacritics support
28
+ - **Remote Loading** — fetch options from API with debounced requests
29
+ - **Option Groups** — grouped options with sticky headers
30
+ - **Keyboard Navigation** — arrows, enter, escape, tab, backspace, ctrl+A
31
+ - **Copy / Paste** — split pasted text into multiple items via delimiter
32
+ - **Custom Rendering** — templates for options, items, create prompt, no-results, loading
33
+ - **Plugin System** — 9 built-in plugins, easy to create custom ones
34
+ - **Accessible** — focus management, label association, keyboard-first
35
+ - **RTL Support** — auto-detected from CSS `direction`
36
+ - **Tailwind CSS** — fully styled with utilities, trivially customizable
37
+ - **Lightweight** — ~25 KB gzipped JS, zero runtime dependencies beyond Alpine.js
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ npm install selectra alpinejs
45
+ ```
46
+
47
+ ### CDN
48
+
49
+ ```html
50
+ <script src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js" defer></script>
51
+ <script src="https://cdn.jsdelivr.net/npm/selectra/dist/selectra.iife.js"></script>
52
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/selectra/dist/selectra.css">
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Quick Start
58
+
59
+ ### 1. Register the Plugin
60
+
61
+ ```js
62
+ import Alpine from 'alpinejs';
63
+ import Selectra from 'selectra';
64
+ import 'selectra/css';
65
+
66
+ Alpine.plugin(Selectra);
67
+ Alpine.start();
68
+ ```
69
+
70
+ ### 2. Use in HTML
71
+
72
+ #### Single Select
73
+
74
+ ```html
75
+ <div x-data="selectra({
76
+ mode: 'single',
77
+ placeholder: 'Select a country...',
78
+ options: [
79
+ { value: 'us', text: 'United States' },
80
+ { value: 'ca', text: 'Canada' },
81
+ { value: 'mx', text: 'Mexico' },
82
+ ]
83
+ })" class="relative max-w-md">
84
+
85
+ <!-- Control -->
86
+ <div @click="focus()"
87
+ :class="{ 'ring-2 ring-blue-500/20 border-blue-500': isFocused }"
88
+ class="relative flex items-center w-full min-h-[42px] px-3 py-1.5 bg-white border border-gray-300 rounded-lg cursor-pointer hover:border-gray-400">
89
+
90
+ <span x-show="items.length && !isFocused" x-text="currentValueText" class="truncate text-gray-900"></span>
91
+
92
+ <input x-ref="searchInput" x-model="query"
93
+ @input="onInput()" @focus="focus()" @blur.debounce.150ms="blur()"
94
+ @keydown="onKeyDown($event)"
95
+ :placeholder="placeholderText"
96
+ x-show="isFocused || !items.length"
97
+ class="flex-1 min-w-0 bg-transparent outline-none border-none p-0 text-gray-900 placeholder-gray-400">
98
+
99
+ <svg :class="{'rotate-180': isOpen}" class="w-4 h-4 text-gray-400 ml-2 transition-transform"
100
+ fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
101
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
102
+ </svg>
103
+ </div>
104
+
105
+ <!-- Dropdown -->
106
+ <div x-show="isOpen" x-ref="dropdown"
107
+ x-transition class="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden">
108
+ <div class="max-h-60 overflow-y-auto py-1">
109
+ <template x-for="(option, index) in filteredOptions" :key="optionKey(option)">
110
+ <div @click="selectOption(option)" @mouseenter="activeIndex = index"
111
+ :class="{ 'bg-blue-500 text-white': activeIndex === index }"
112
+ class="px-3 py-2 cursor-pointer"
113
+ x-html="renderOption(option)">
114
+ </div>
115
+ </template>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ ```
120
+
121
+ #### Multi Select with Tags
122
+
123
+ ```html
124
+ <div x-data="selectra({
125
+ mode: 'multi',
126
+ placeholder: 'Select languages...',
127
+ create: true,
128
+ options: [
129
+ { value: 'js', text: 'JavaScript' },
130
+ { value: 'py', text: 'Python' },
131
+ { value: 'go', text: 'Go' },
132
+ ]
133
+ })" class="relative max-w-md">
134
+
135
+ <div @click="focus()"
136
+ :class="{ 'ring-2 ring-blue-500/20 border-blue-500': isFocused }"
137
+ class="relative flex flex-wrap items-center gap-1 w-full min-h-[42px] px-2 py-1.5 bg-white border border-gray-300 rounded-lg cursor-text hover:border-gray-400">
138
+
139
+ <template x-for="val in items" :key="val">
140
+ <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-blue-50 text-blue-700 border border-blue-200 text-sm">
141
+ <span x-text="options[val]?.text || val"></span>
142
+ <button @click.stop="removeItem(val)" class="w-4 h-4 rounded-full text-blue-400 hover:text-blue-600">&times;</button>
143
+ </span>
144
+ </template>
145
+
146
+ <input x-ref="searchInput" x-model="query"
147
+ @input="onInput()" @focus="focus()" @blur.debounce.150ms="blur()"
148
+ @keydown="onKeyDown($event)" @paste="onPaste($event)"
149
+ :placeholder="items.length ? '' : placeholderText"
150
+ class="flex-1 min-w-[60px] bg-transparent outline-none border-none p-0 text-sm">
151
+ </div>
152
+
153
+ <div x-show="isOpen" x-ref="dropdown" x-transition
154
+ class="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden">
155
+ <div class="max-h-60 overflow-y-auto py-1">
156
+ <template x-for="(option, index) in filteredOptions" :key="optionKey(option)">
157
+ <div @click="selectOption(option)" @mouseenter="activeIndex = index"
158
+ :class="{ 'bg-blue-500 text-white': activeIndex === index }"
159
+ class="px-3 py-2 cursor-pointer" x-html="renderOption(option)">
160
+ </div>
161
+ </template>
162
+ <div x-show="canCreate" @click="createItem()"
163
+ class="px-3 py-2 cursor-pointer text-gray-500 border-t border-gray-100"
164
+ x-html="renderOptionCreate()">
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ ```
170
+
171
+ ## Configuration
172
+
173
+ | Option | Type | Default | Description |
174
+ |--------|------|---------|-------------|
175
+ | `mode` | `'single' \| 'multi'` | auto | Selection mode |
176
+ | `options` | `Array` | `[]` | Available options |
177
+ | `items` | `Array` | `[]` | Pre-selected values |
178
+ | `maxItems` | `number \| null` | `null` | Max selectable items |
179
+ | `maxOptions` | `number` | `1000` | Max dropdown options |
180
+ | `create` | `boolean \| function` | `false` | Allow creating new options |
181
+ | `createOnBlur` | `boolean` | `false` | Create item when field loses focus |
182
+ | `createFilter` | `RegExp \| function` | `null` | Filter for creatable values |
183
+ | `placeholder` | `string` | `''` | Placeholder text |
184
+ | `valueField` | `string` | `'value'` | Property for option value |
185
+ | `labelField` | `string` | `'text'` | Property for display label |
186
+ | `searchField` | `string[]` | `['text']` | Fields to search |
187
+ | `searchConjunction` | `'and' \| 'or'` | `'and'` | Multi-term search logic |
188
+ | `sortField` | `string \| Array` | `'$order'` | Sort field(s) |
189
+ | `highlight` | `boolean` | `true` | Highlight matches |
190
+ | `openOnFocus` | `boolean` | `true` | Open dropdown on focus |
191
+ | `selectOnTab` | `boolean` | `true` | Tab selects active option |
192
+ | `closeAfterSelect` | `boolean` | `false` | Close dropdown after selection |
193
+ | `hideSelected` | `boolean \| null` | auto | Hide selected from dropdown |
194
+ | `delimiter` | `string` | `','` | Value separator |
195
+ | `splitOn` | `RegExp \| string` | `null` | Regex for splitting pasted values |
196
+ | `diacritics` | `boolean` | `true` | International character support |
197
+ | `normalize` | `boolean` | `true` | NFD normalize search queries |
198
+ | `preload` | `boolean \| 'focus'` | `false` | Preload options |
199
+ | `load` | `function \| null` | `null` | Remote data loader |
200
+ | `loadThrottle` | `number` | `300` | Load debounce delay (ms) |
201
+ | `plugins` | `Array` | `[]` | Plugins to activate |
202
+
203
+ ### Custom Rendering
204
+
205
+ ```js
206
+ selectra({
207
+ render: {
208
+ option: (data, escape) => `<div class="flex items-center gap-2">
209
+ <img src="${data.avatar}" class="w-6 h-6 rounded-full">
210
+ <span>${escape(data.text)}</span>
211
+ </div>`,
212
+ item: (data, escape) => escape(data.text),
213
+ optionCreate: (data, escape) => `Add "${escape(data.input)}"`,
214
+ noResults: () => 'Nothing found',
215
+ loading: () => 'Searching...',
216
+ }
217
+ })
218
+ ```
219
+
220
+ ### Remote Loading
221
+
222
+ ```js
223
+ selectra({
224
+ load: (query, callback) => {
225
+ fetch(`/api/search?q=${encodeURIComponent(query)}`)
226
+ .then(res => res.json())
227
+ .then(data => callback(data.results))
228
+ .catch(() => callback([]));
229
+ },
230
+ loadThrottle: 300,
231
+ })
232
+ ```
233
+
234
+ ## API Methods
235
+
236
+ | Method | Description |
237
+ |--------|-------------|
238
+ | `addOption(data)` | Add one or more options |
239
+ | `updateOption(value, data)` | Update an existing option |
240
+ | `removeOption(value)` | Remove an option |
241
+ | `clearOptions()` | Remove all options (keeps selected) |
242
+ | `getOption(value)` | Get option data by value |
243
+ | `addItem(value)` | Select an item |
244
+ | `removeItem(value)` | Deselect an item |
245
+ | `clear()` | Clear all selections |
246
+ | `getValue()` | Get current value(s) |
247
+ | `setValue(value)` | Set selected value(s) |
248
+ | `createItem(input?)` | Create new item from input |
249
+ | `open()` | Open dropdown |
250
+ | `close()` | Close dropdown |
251
+ | `focus()` | Focus the control |
252
+ | `blur()` | Remove focus |
253
+ | `lock()` / `unlock()` | Temporarily disable input |
254
+ | `disable()` / `enable()` | Disable/enable control |
255
+ | `setMaxItems(max)` | Update max items limit |
256
+
257
+ ## Events
258
+
259
+ Events are dispatched as custom DOM events on the wrapper element:
260
+
261
+ ```js
262
+ el.addEventListener('selectra:change', (e) => {
263
+ console.log('Value changed:', e.detail);
264
+ });
265
+ ```
266
+
267
+ | Event | Detail |
268
+ |-------|--------|
269
+ | `selectra:change` | `[value]` |
270
+ | `selectra:itemadd` | `[value, data]` |
271
+ | `selectra:itemremove` | `[value]` |
272
+ | `selectra:clear` | `[]` |
273
+ | `selectra:optionadd` | `[value, data]` |
274
+ | `selectra:optionremove` | `[value]` |
275
+ | `selectra:dropdownopen` | `[]` |
276
+ | `selectra:dropdownclose` | `[]` |
277
+ | `selectra:type` | `[query]` |
278
+ | `selectra:focus` | `[]` |
279
+ | `selectra:blur` | `[]` |
280
+ | `selectra:initialize` | `[]` |
281
+
282
+ ## Plugins
283
+
284
+ ### Built-in Plugins
285
+
286
+ Activate plugins via the `plugins` config option:
287
+
288
+ ```js
289
+ selectra({
290
+ plugins: ['remove_button', 'clear_button', { name: 'tag_limit', options: { tagLimit: 3 } }]
291
+ })
292
+ ```
293
+
294
+ | Plugin | Description |
295
+ |--------|-------------|
296
+ | `remove_button` | Add × button to each selected tag |
297
+ | `clear_button` | Add a clear-all button to the control |
298
+ | `restore_on_backspace` | Restore deleted item text to input |
299
+ | `dropdown_header` | Add a header to the dropdown |
300
+ | `tag_limit` | Limit visible tags with "+N" badge |
301
+ | `auto_select_on_type` | Auto-select first match on blur |
302
+ | `select_on_focus` | Put current value into search on focus |
303
+ | `read_only` | Make component read-only |
304
+ | `auto_position` | Smart dropdown positioning (above/below) |
305
+
306
+ ### Custom Plugins
307
+
308
+ ```js
309
+ import { registerPlugin } from 'selectra';
310
+
311
+ registerPlugin('my_plugin', function(options) {
312
+ // `this` is the selectra component instance
313
+ console.log('Plugin initialized with', options);
314
+
315
+ // Override methods
316
+ const originalOpen = this.open.bind(this);
317
+ this.open = () => {
318
+ console.log('Opening dropdown');
319
+ originalOpen();
320
+ };
321
+ });
322
+ ```
323
+
324
+ ## Building
325
+
326
+ ```bash
327
+ npm install
328
+ npm run build # Build dist/
329
+ npm run dev # Dev server with HMR
330
+ npm run test # Run tests
331
+ ```
332
+
333
+ ## Migration from jQuery Version
334
+
335
+ | jQuery Selectize | Selectra |
336
+ |-----------------|---------------------|
337
+ | `$('select').selectize({...})` | `x-data="selectra({...})"` |
338
+ | `instance.addItem(val)` | Direct method call in Alpine scope |
339
+ | `$.fn.selectize` plugin | `Alpine.plugin(Selectra)` |
340
+ | jQuery events | Custom DOM events |
341
+ | LESS/SCSS themes | Tailwind CSS utilities |
342
+ | Bootstrap themes | Use Tailwind directly |
343
+
344
+ ## License
345
+
346
+ Apache-2.0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Selectra - Alpine.js + Tailwind CSS
3
+ * TypeScript declarations
4
+ */
5
+
6
+ import type { Alpine } from 'alpinejs';
7
+
8
+ export interface SelectraOption {
9
+ [key: string]: any;
10
+ value?: string;
11
+ text?: string;
12
+ disabled?: boolean;
13
+ optgroup?: string;
14
+ $order?: number;
15
+ }
16
+
17
+ export interface SelectraOptgroup {
18
+ value: string;
19
+ label: string;
20
+ disabled?: boolean;
21
+ }
22
+
23
+ export interface SelectraRender {
24
+ option?: (data: SelectraOption, escape: (str: string) => string) => string;
25
+ item?: (data: SelectraOption, escape: (str: string) => string) => string;
26
+ optionCreate?: (data: { input: string }, escape: (str: string) => string) => string;
27
+ optgroupHeader?: (data: SelectraOptgroup, escape: (str: string) => string) => string;
28
+ noResults?: (data: { query: string }, escape: (str: string) => string) => string;
29
+ loading?: (data: { query: string }, escape: (str: string) => string) => string;
30
+ }
31
+
32
+ export interface SelectraConfig {
33
+ delimiter?: string;
34
+ splitOn?: RegExp | string | null;
35
+ persist?: boolean;
36
+ diacritics?: boolean;
37
+ create?: boolean | ((input: string, callback: (data?: SelectraOption) => void) => SelectraOption | void);
38
+ showAddOptionOnCreate?: boolean;
39
+ createOnBlur?: boolean;
40
+ createFilter?: RegExp | string | ((input: string) => boolean) | null;
41
+ highlight?: boolean;
42
+ openOnFocus?: boolean;
43
+ maxOptions?: number;
44
+ maxItems?: number | null;
45
+ hideSelected?: boolean | null;
46
+ selectOnTab?: boolean;
47
+ closeAfterSelect?: boolean;
48
+ loadThrottle?: number | null;
49
+ placeholder?: string;
50
+ mode?: 'single' | 'multi' | null;
51
+ search?: boolean;
52
+ showArrow?: boolean;
53
+ normalize?: boolean;
54
+
55
+ valueField?: string;
56
+ labelField?: string;
57
+ disabledField?: string;
58
+ optgroupField?: string;
59
+ optgroupLabelField?: string;
60
+ optgroupValueField?: string;
61
+ sortField?: string | Array<{ field: string; direction?: 'asc' | 'desc' }>;
62
+ searchField?: string | string[];
63
+ searchConjunction?: 'and' | 'or';
64
+ respectWordBoundaries?: boolean;
65
+ setFirstOptionActive?: boolean;
66
+ preload?: boolean | 'focus';
67
+
68
+ options?: SelectraOption[];
69
+ optgroups?: SelectraOptgroup[];
70
+ items?: string[];
71
+ plugins?: Array<string | { name: string; options?: Record<string, any> }>;
72
+
73
+ render?: SelectraRender;
74
+
75
+ load?: ((query: string, callback: (results: SelectraOption[]) => void) => void) | null;
76
+ score?: ((search: any) => (item: any) => number) | null;
77
+
78
+ onChange?: (value: string | string[]) => void;
79
+ onItemAdd?: (value: string, data: SelectraOption) => void;
80
+ onItemRemove?: (value: string) => void;
81
+ onClear?: () => void;
82
+ onOptionAdd?: (value: string, data: SelectraOption) => void;
83
+ onOptionRemove?: (value: string) => void;
84
+ onDropdownOpen?: () => void;
85
+ onDropdownClose?: () => void;
86
+ onType?: (query: string) => void;
87
+ onFocus?: () => void;
88
+ onBlur?: () => void;
89
+ onInitialize?: () => void;
90
+ }
91
+
92
+ export interface SelectraInstance {
93
+ // State
94
+ isOpen: boolean;
95
+ isFocused: boolean;
96
+ isDisabled: boolean;
97
+ isLocked: boolean;
98
+ isLoading: boolean;
99
+ isInvalid: boolean;
100
+ query: string;
101
+ activeIndex: number;
102
+ items: string[];
103
+ options: Record<string, SelectraOption>;
104
+ optgroups: Record<string, SelectraOptgroup>;
105
+
106
+ // Computed
107
+ readonly isMultiple: boolean;
108
+ readonly isSingle: boolean;
109
+ readonly isFull: boolean;
110
+ readonly hasOptions: boolean;
111
+ readonly canCreate: boolean;
112
+ readonly selectedItems: SelectraOption[];
113
+ readonly filteredOptions: SelectraOption[];
114
+ readonly placeholderText: string;
115
+ readonly currentValueText: string;
116
+
117
+ // Methods
118
+ init(): void;
119
+ destroy(): void;
120
+
121
+ addOption(data: SelectraOption | SelectraOption[], silent?: boolean): void;
122
+ updateOption(value: string, data: SelectraOption): void;
123
+ removeOption(value: string): void;
124
+ clearOptions(): void;
125
+ getOption(value: string): SelectraOption | null;
126
+
127
+ addItem(value: string, silent?: boolean): void;
128
+ removeItem(value: string, silent?: boolean): void;
129
+ clear(silent?: boolean): void;
130
+ getValue(): string | string[];
131
+ setValue(value: string | string[], silent?: boolean): void;
132
+ createItem(input?: string | null): void;
133
+
134
+ selectOption(option: SelectraOption): void;
135
+
136
+ open(): void;
137
+ close(): void;
138
+ toggle(): void;
139
+ focus(): void;
140
+ blur(): void;
141
+
142
+ lock(): void;
143
+ unlock(): void;
144
+ disable(): void;
145
+ enable(): void;
146
+ setMaxItems(max: number | null): void;
147
+
148
+ addOptionGroup(id: string, data: SelectraOptgroup): void;
149
+ removeOptionGroup(id: string): void;
150
+ getGroupedOptions(): Array<{ id: string | null; label: string | null; options: SelectraOption[] }>;
151
+
152
+ renderOption(option: SelectraOption): string;
153
+ renderItem(option: SelectraOption): string;
154
+ renderOptionCreate(): string;
155
+ renderNoResults(): string;
156
+
157
+ onKeyDown(e: KeyboardEvent): void;
158
+ onInput(): void;
159
+ onPaste(e: ClipboardEvent): void;
160
+
161
+ isSelected(option: SelectraOption): boolean;
162
+ optionKey(option: SelectraOption): string | null;
163
+ }
164
+
165
+ /**
166
+ * Alpine.js plugin for Selectra
167
+ */
168
+ declare function SelectraPlugin(Alpine: Alpine): void;
169
+
170
+ export default SelectraPlugin;
171
+
172
+ /**
173
+ * Create a selectra component data factory
174
+ */
175
+ export function createSelectraComponent(config?: SelectraConfig): () => SelectraInstance;
176
+
177
+ /**
178
+ * Register a plugin
179
+ */
180
+ export function registerPlugin(name: string, fn: (this: SelectraInstance, options?: any) => void): void;
181
+
182
+ /**
183
+ * Get default configuration
184
+ */
185
+ export function getDefaults(): SelectraConfig;
186
+
187
+ export const DEFAULTS: SelectraConfig;
188
+
189
+ /**
190
+ * Sifter search engine
191
+ */
192
+ export class Sifter {
193
+ constructor(items: Record<string, any> | any[], settings?: { diacritics?: boolean });
194
+ items: Record<string, any> | any[];
195
+ tokenize(query: string, respectWordBoundaries?: boolean): Array<{ string: string; regex: RegExp }>;
196
+ search(query: string, options?: {
197
+ fields?: string | string[];
198
+ sort?: Array<{ field: string; direction?: 'asc' | 'desc' }>;
199
+ score?: (item: any) => number;
200
+ filter?: boolean;
201
+ limit?: number;
202
+ conjunction?: 'and' | 'or';
203
+ nesting?: boolean;
204
+ respect_word_boundaries?: boolean;
205
+ }): {
206
+ query: string;
207
+ tokens: Array<{ string: string; regex: RegExp }>;
208
+ total: number;
209
+ items: Array<{ score: number; id: string | number }>;
210
+ };
211
+ getScoreFunction(search: any, options?: any): (item: any) => number;
212
+ getSortFunction(search: any, options?: any): ((a: any, b: any) => number) | null;
213
+ prepareSearch(query: string | object, options?: any): any;
214
+ }
215
+
216
+ export function escapeHtml(str: string): string;
217
+ export function hashKey(value: any): string | null;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Selectra - Alpine.js Plugin
3
+ *
4
+ * A powerful, extensible <select> UI control for tagging, contact lists,
5
+ * country selectors, and autocomplete. Built with Alpine.js and Tailwind CSS.
6
+ *
7
+ * @license Apache-2.0
8
+ *
9
+ * Usage:
10
+ *
11
+ * // Register the plugin
12
+ * import Alpine from 'alpinejs';
13
+ * import Selectra from 'selectra';
14
+ * Alpine.plugin(Selectra);
15
+ * Alpine.start();
16
+ *
17
+ * // In HTML (directive approach):
18
+ * <div x-data="selectra({ options: [...], create: true })">
19
+ * <template x-selectra></template>
20
+ * </div>
21
+ *
22
+ * // Or initialize on existing <select>:
23
+ * <div x-data="selectra()" x-selectra>
24
+ * <select>
25
+ * <option value="1">One</option>
26
+ * <option value="2">Two</option>
27
+ * </select>
28
+ * </div>
29
+ */
30
+
31
+ import { createSelectizeComponent, registerPlugin, getDefaults, DEFAULTS } from './selectize.js';
32
+ import { escapeHtml, hashKey, autoGrow } from './utils.js';
33
+ import Sifter from './sifter.js';
34
+
35
+ // Import Tailwind CSS styles
36
+ import './styles/selectra.css';
37
+
38
+ // Load built-in plugins
39
+ import './plugins/index.js';
40
+
41
+ /**
42
+ * Alpine.js plugin installer
43
+ */
44
+ function SelectraPlugin(Alpine) {
45
+ // Register the selectra data component
46
+ Alpine.data('selectra', (config = {}) => {
47
+ const componentFactory = createSelectizeComponent(config);
48
+ return componentFactory();
49
+ });
50
+
51
+ // Register the x-selectra directive for auto-rendering
52
+ Alpine.directive('selectra', (el, { expression }, { evaluate, cleanup }) => {
53
+ // The directive is mainly a marker — the template is in the component.
54
+ // It can be used with x-data="selectra({...})" for auto-init.
55
+ });
56
+ }
57
+
58
+ SelectraPlugin.version = '1.0.0';
59
+
60
+ // Named exports
61
+ export {
62
+ SelectraPlugin as default,
63
+ createSelectizeComponent,
64
+ registerPlugin,
65
+ getDefaults,
66
+ DEFAULTS,
67
+ Sifter,
68
+ escapeHtml,
69
+ hashKey,
70
+ };