@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.
- package/LICENSE +203 -0
- package/README.md +346 -0
- package/dist/selectra.css +1 -0
- package/dist/selectra.es.js +1318 -0
- package/dist/selectra.es.js.map +1 -0
- package/dist/selectra.iife.js +32 -0
- package/dist/selectra.iife.js.map +1 -0
- package/dist/selectra.umd.js +32 -0
- package/dist/selectra.umd.js.map +1 -0
- package/package.json +64 -0
- package/src-alpine/README.md +346 -0
- package/src-alpine/index.d.ts +217 -0
- package/src-alpine/index.js +70 -0
- package/src-alpine/plugins/index.js +219 -0
- package/src-alpine/selectize.js +1022 -0
- package/src-alpine/sifter.js +270 -0
- package/src-alpine/styles/selectra.css +277 -0
- package/src-alpine/utils.js +181 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selectra Plugins
|
|
3
|
+
*
|
|
4
|
+
* Each plugin is a function that receives options and is called with
|
|
5
|
+
* `this` bound to the selectra component instance. Plugins can modify
|
|
6
|
+
* component behavior by wrapping methods or adding new properties.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { registerPlugin } from '../selectize.js';
|
|
10
|
+
|
|
11
|
+
// ─── Remove Button Plugin ───────────────────────────────────
|
|
12
|
+
registerPlugin('remove_button', function (options = {}) {
|
|
13
|
+
const {
|
|
14
|
+
label = '×',
|
|
15
|
+
title = 'Remove',
|
|
16
|
+
className = '',
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
// Store original renderItem
|
|
20
|
+
const originalRenderItem = this._config.render?.item;
|
|
21
|
+
|
|
22
|
+
// Override item rendering to include remove button
|
|
23
|
+
if (!this._config.render) this._config.render = {};
|
|
24
|
+
|
|
25
|
+
const self = this;
|
|
26
|
+
this._config.render.item = function (data, escape) {
|
|
27
|
+
const labelField = self._config.labelField;
|
|
28
|
+
const valueField = self._config.valueField;
|
|
29
|
+
const text = originalRenderItem
|
|
30
|
+
? originalRenderItem(data, escape)
|
|
31
|
+
: escape(data[labelField] || '');
|
|
32
|
+
|
|
33
|
+
return `<span class="inline-flex items-center">${text}</span>`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// The remove button is rendered directly in the template via the
|
|
37
|
+
// showRemoveButton flag
|
|
38
|
+
this._showRemoveButton = true;
|
|
39
|
+
this._removeButtonLabel = label;
|
|
40
|
+
this._removeButtonTitle = title;
|
|
41
|
+
this._removeButtonClass = className;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ─── Clear Button Plugin ────────────────────────────────────
|
|
45
|
+
registerPlugin('clear_button', function (options = {}) {
|
|
46
|
+
const {
|
|
47
|
+
title = 'Clear All',
|
|
48
|
+
className = '',
|
|
49
|
+
label = '×',
|
|
50
|
+
} = options;
|
|
51
|
+
|
|
52
|
+
this._showClearButton = true;
|
|
53
|
+
this._clearButtonTitle = title;
|
|
54
|
+
this._clearButtonLabel = label;
|
|
55
|
+
this._clearButtonClass = className;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ─── Restore on Backspace Plugin ────────────────────────────
|
|
59
|
+
registerPlugin('restore_on_backspace', function (options = {}) {
|
|
60
|
+
const textFn = options.text || ((opt) => opt[this._config.labelField] || '');
|
|
61
|
+
const originalOnKeyDown = this.onKeyDown.bind(this);
|
|
62
|
+
|
|
63
|
+
this.onKeyDown = (e) => {
|
|
64
|
+
if (e.key === 'Backspace' && !this.query && this.items.length && this.isMultiple) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
const lastValue = this.items[this.items.length - 1];
|
|
67
|
+
const lastOption = this.options[lastValue];
|
|
68
|
+
this.removeItem(lastValue);
|
|
69
|
+
if (lastOption) {
|
|
70
|
+
this.query = textFn(lastOption);
|
|
71
|
+
if (this.$refs.searchInput) {
|
|
72
|
+
this.$refs.searchInput.value = this.query;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
originalOnKeyDown(e);
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ─── Dropdown Header Plugin ─────────────────────────────────
|
|
82
|
+
registerPlugin('dropdown_header', function (options = {}) {
|
|
83
|
+
const {
|
|
84
|
+
title = '',
|
|
85
|
+
showClose = true,
|
|
86
|
+
headerClass = '',
|
|
87
|
+
} = options;
|
|
88
|
+
|
|
89
|
+
this._dropdownHeader = true;
|
|
90
|
+
this._dropdownHeaderTitle = title;
|
|
91
|
+
this._dropdownHeaderShowClose = showClose;
|
|
92
|
+
this._dropdownHeaderClass = headerClass;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ─── Tag Limit Plugin ───────────────────────────────────────
|
|
96
|
+
registerPlugin('tag_limit', function (options = {}) {
|
|
97
|
+
const { tagLimit = 3 } = options;
|
|
98
|
+
|
|
99
|
+
this._tagLimit = tagLimit;
|
|
100
|
+
this._showAllTags = false;
|
|
101
|
+
|
|
102
|
+
// Override selectedItems to respect tag limit
|
|
103
|
+
Object.defineProperty(this, 'visibleItems', {
|
|
104
|
+
get() {
|
|
105
|
+
const all = this.selectedItems;
|
|
106
|
+
if (this.isFocused || this._showAllTags || !this._tagLimit) return all;
|
|
107
|
+
return all.slice(0, this._tagLimit);
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
Object.defineProperty(this, 'hiddenItemCount', {
|
|
112
|
+
get() {
|
|
113
|
+
const all = this.selectedItems;
|
|
114
|
+
if (this.isFocused || this._showAllTags || !this._tagLimit) return 0;
|
|
115
|
+
return Math.max(0, all.length - this._tagLimit);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ─── Auto Select on Type Plugin ─────────────────────────────
|
|
121
|
+
registerPlugin('auto_select_on_type', function () {
|
|
122
|
+
const originalBlur = this.blur.bind(this);
|
|
123
|
+
|
|
124
|
+
this.blur = () => {
|
|
125
|
+
if (this.query.trim() && this.filteredOptions.length) {
|
|
126
|
+
const first = this.filteredOptions[0];
|
|
127
|
+
this.selectOption(first);
|
|
128
|
+
}
|
|
129
|
+
this.query = '';
|
|
130
|
+
originalBlur();
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ─── Select on Focus Plugin ─────────────────────────────────
|
|
135
|
+
registerPlugin('select_on_focus', function () {
|
|
136
|
+
const originalFocus = this.focus.bind(this);
|
|
137
|
+
|
|
138
|
+
this.focus = () => {
|
|
139
|
+
originalFocus();
|
|
140
|
+
if (this.isSingle && this.items.length) {
|
|
141
|
+
const current = this.options[this.items[0]];
|
|
142
|
+
if (current) {
|
|
143
|
+
this.query = current[this._config.labelField] || '';
|
|
144
|
+
if (this.$refs.searchInput) {
|
|
145
|
+
this.$nextTick(() => this.$refs.searchInput.select());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ─── Read-Only Plugin ───────────────────────────────────────
|
|
153
|
+
registerPlugin('read_only', function (options = {}) {
|
|
154
|
+
const { readOnly = true } = options;
|
|
155
|
+
this._isReadOnly = readOnly;
|
|
156
|
+
|
|
157
|
+
this.readonly = (value) => {
|
|
158
|
+
this._isReadOnly = value !== undefined ? value : !this._isReadOnly;
|
|
159
|
+
if (this._isReadOnly) {
|
|
160
|
+
this.close();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Override to prevent modifications in read-only mode
|
|
165
|
+
const originalAddItem = this.addItem.bind(this);
|
|
166
|
+
const originalRemoveItem = this.removeItem.bind(this);
|
|
167
|
+
const originalCreateItem = this.createItem.bind(this);
|
|
168
|
+
const originalClear = this.clear.bind(this);
|
|
169
|
+
|
|
170
|
+
this.addItem = (value, silent) => {
|
|
171
|
+
if (this._isReadOnly) return;
|
|
172
|
+
originalAddItem(value, silent);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
this.removeItem = (value, silent) => {
|
|
176
|
+
if (this._isReadOnly) return;
|
|
177
|
+
originalRemoveItem(value, silent);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
this.createItem = (input) => {
|
|
181
|
+
if (this._isReadOnly) return;
|
|
182
|
+
originalCreateItem(input);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.clear = (silent) => {
|
|
186
|
+
if (this._isReadOnly) return;
|
|
187
|
+
originalClear(silent);
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ─── Auto Position Plugin ───────────────────────────────────
|
|
192
|
+
registerPlugin('auto_position', function () {
|
|
193
|
+
this._autoPosition = true;
|
|
194
|
+
|
|
195
|
+
const originalOpen = this.open.bind(this);
|
|
196
|
+
this.open = () => {
|
|
197
|
+
originalOpen();
|
|
198
|
+
this.$nextTick(() => {
|
|
199
|
+
const dropdown = this.$refs.dropdown;
|
|
200
|
+
const wrapper = this.$el;
|
|
201
|
+
if (!dropdown || !wrapper) return;
|
|
202
|
+
|
|
203
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
204
|
+
const spaceBelow = window.innerHeight - wrapperRect.bottom;
|
|
205
|
+
const spaceAbove = wrapperRect.top;
|
|
206
|
+
const dropdownHeight = dropdown.offsetHeight || 250;
|
|
207
|
+
|
|
208
|
+
if (spaceBelow < dropdownHeight && spaceAbove > spaceBelow) {
|
|
209
|
+
this._dropdownPosition = 'top';
|
|
210
|
+
} else {
|
|
211
|
+
this._dropdownPosition = 'bottom';
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
this._dropdownPosition = 'bottom';
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export default {};
|