@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,181 @@
1
+ /**
2
+ * Utility functions for Selectize Alpine.js
3
+ */
4
+
5
+ /**
6
+ * Escape HTML entities to prevent XSS
7
+ */
8
+ export function escapeHtml(str) {
9
+ if (typeof str !== 'string') return '';
10
+ return str
11
+ .replace(/&/g, '&')
12
+ .replace(/</g, '&lt;')
13
+ .replace(/>/g, '&gt;')
14
+ .replace(/"/g, '&quot;');
15
+ }
16
+
17
+ /**
18
+ * Debounce a function call
19
+ */
20
+ export function debounce(fn, delay) {
21
+ let timer;
22
+ return function (...args) {
23
+ clearTimeout(timer);
24
+ timer = setTimeout(() => fn.apply(this, args), delay);
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Generate a unique ID
30
+ */
31
+ let idCounter = 0;
32
+ export function uid(prefix = 'selectize') {
33
+ return `${prefix}-${++idCounter}-${Math.random().toString(36).slice(2, 8)}`;
34
+ }
35
+
36
+ /**
37
+ * Hash a value to a string key
38
+ */
39
+ export function hashKey(value) {
40
+ if (typeof value === 'undefined' || value === null) return null;
41
+ return typeof value === 'boolean'
42
+ ? value ? '1' : '0'
43
+ : String(value);
44
+ }
45
+
46
+ /**
47
+ * Highlight matching text in a string
48
+ */
49
+ export function highlight(text, search) {
50
+ if (!search || !search.length) return escapeHtml(text);
51
+ const escaped = escapeHtml(text);
52
+ const regex = new RegExp(
53
+ '(' + search.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1') + ')',
54
+ 'ig'
55
+ );
56
+ return escaped.replace(regex, '<span class="font-semibold text-inherit">$1</span>');
57
+ }
58
+
59
+ /**
60
+ * Measure the width of a string in pixels
61
+ */
62
+ let measureCanvas;
63
+ export function measureString(str, el) {
64
+ if (!measureCanvas) {
65
+ measureCanvas = document.createElement('canvas');
66
+ }
67
+ const ctx = measureCanvas.getContext('2d');
68
+ if (el) {
69
+ const style = window.getComputedStyle(el);
70
+ ctx.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
71
+ }
72
+ return ctx.measureText(str).width;
73
+ }
74
+
75
+ /**
76
+ * Auto-grow an input element to fit its content
77
+ */
78
+ export function autoGrow(input, extraWidth = 10) {
79
+ const val = input.value || input.placeholder || '';
80
+ const width = measureString(val, input) + extraWidth;
81
+ input.style.width = Math.max(width, 60) + 'px';
82
+ }
83
+
84
+ /**
85
+ * Deep merge objects (simple version)
86
+ */
87
+ export function deepMerge(target, ...sources) {
88
+ for (const source of sources) {
89
+ if (!source) continue;
90
+ for (const key of Object.keys(source)) {
91
+ if (
92
+ source[key] &&
93
+ typeof source[key] === 'object' &&
94
+ !Array.isArray(source[key])
95
+ ) {
96
+ target[key] = deepMerge(target[key] || {}, source[key]);
97
+ } else {
98
+ target[key] = source[key];
99
+ }
100
+ }
101
+ }
102
+ return target;
103
+ }
104
+
105
+ /**
106
+ * Determine if the given element is a <select> tag
107
+ */
108
+ export function isSelectElement(el) {
109
+ return el.tagName && el.tagName.toLowerCase() === 'select';
110
+ }
111
+
112
+ /**
113
+ * Determine if the given element is an <input> tag
114
+ */
115
+ export function isInputElement(el) {
116
+ return el.tagName && el.tagName.toLowerCase() === 'input';
117
+ }
118
+
119
+ /**
120
+ * Read options from a native <select> element
121
+ */
122
+ export function readSelectOptions(selectEl) {
123
+ const options = [];
124
+ const optgroups = [];
125
+ const selectedValues = [];
126
+
127
+ const processOption = (optionEl, optgroup = null) => {
128
+ const value = optionEl.value;
129
+ const text = optionEl.textContent.trim();
130
+ const disabled = optionEl.disabled;
131
+
132
+ if (!value && !text) return;
133
+
134
+ const data = { value, text, disabled };
135
+
136
+ // Read data-* attributes
137
+ if (optionEl.dataset.data) {
138
+ try {
139
+ Object.assign(data, JSON.parse(optionEl.dataset.data));
140
+ } catch (_) {
141
+ // ignore
142
+ }
143
+ }
144
+
145
+ if (optgroup !== null) {
146
+ data.optgroup = optgroup;
147
+ }
148
+
149
+ if (optionEl.selected) {
150
+ selectedValues.push(value);
151
+ }
152
+
153
+ options.push(data);
154
+ };
155
+
156
+ for (const child of selectEl.children) {
157
+ if (child.tagName.toLowerCase() === 'optgroup') {
158
+ const groupId = child.getAttribute('label') || '';
159
+ optgroups.push({
160
+ value: groupId,
161
+ label: groupId,
162
+ disabled: child.disabled,
163
+ });
164
+ for (const opt of child.children) {
165
+ processOption(opt, groupId);
166
+ }
167
+ } else if (child.tagName.toLowerCase() === 'option') {
168
+ processOption(child);
169
+ }
170
+ }
171
+
172
+ return { options, optgroups, selectedValues };
173
+ }
174
+
175
+ /**
176
+ * Check if element is RTL
177
+ */
178
+ export function isRtl(el) {
179
+ const style = window.getComputedStyle(el);
180
+ return style.direction === 'rtl';
181
+ }