@enigmax/primitives 0.1.2 → 0.2.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/dist/chunk-OKNWKBQI.js +617 -0
- package/dist/index.d.ts +143 -1
- package/dist/index.js +1 -1
- package/dist/react/index.d.ts +53 -3
- package/dist/react/index.js +81 -3
- package/package.json +13 -4
- package/registry.json +137 -7
- package/src/core/input.ts +315 -0
- package/src/core/search.ts +225 -0
- package/src/index.ts +2 -0
- package/src/react/index.ts +4 -0
- package/src/react/use-input.ts +70 -0
- package/src/react/use-search.ts +77 -0
- package/dist/chunk-55J5JEWX.js +0 -288
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
// src/core/marquee.ts
|
|
2
|
+
var DEFAULT_DECAY = 0.12;
|
|
3
|
+
var DRAG_THRESHOLD = 6;
|
|
4
|
+
var STILL_MS = 90;
|
|
5
|
+
var MAX_DT = 0.05;
|
|
6
|
+
var VELOCITY_SMOOTHING = 0.6;
|
|
7
|
+
var TOUCH_HOVER_SUPPRESS_MS = 1e3;
|
|
8
|
+
var IDLE_EPSILON = 0.01;
|
|
9
|
+
var DEFAULTS = {
|
|
10
|
+
speed: 60,
|
|
11
|
+
reverse: false,
|
|
12
|
+
vertical: false,
|
|
13
|
+
draggable: true,
|
|
14
|
+
hoverScale: 1,
|
|
15
|
+
decay: DEFAULT_DECAY,
|
|
16
|
+
manageStyles: true,
|
|
17
|
+
copies: "clone"
|
|
18
|
+
};
|
|
19
|
+
function createMarquee(lane, track, options = {}) {
|
|
20
|
+
let opts = { ...DEFAULTS, ...options };
|
|
21
|
+
let period = 0;
|
|
22
|
+
let copyCount = Math.max(2, track.children.length);
|
|
23
|
+
let offset = 0;
|
|
24
|
+
let velocity = 0;
|
|
25
|
+
let running = false;
|
|
26
|
+
let paused = false;
|
|
27
|
+
let destroyed = false;
|
|
28
|
+
let frame = 0;
|
|
29
|
+
let lastTime = 0;
|
|
30
|
+
let dragging = false;
|
|
31
|
+
let pointerId = null;
|
|
32
|
+
let lastPointer = 0;
|
|
33
|
+
let startPointer = 0;
|
|
34
|
+
let lastMoveTime = 0;
|
|
35
|
+
let maxTravel = 0;
|
|
36
|
+
let hovering = false;
|
|
37
|
+
let lastTouchAt = 0;
|
|
38
|
+
const motionQuery = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
39
|
+
let reducedMotion = motionQuery?.matches ?? false;
|
|
40
|
+
function target() {
|
|
41
|
+
if (reducedMotion || paused) return 0;
|
|
42
|
+
const cruise = opts.reverse ? -opts.speed : opts.speed;
|
|
43
|
+
return hovering ? cruise * opts.hoverScale : cruise;
|
|
44
|
+
}
|
|
45
|
+
function axisSize(element) {
|
|
46
|
+
return opts.vertical ? element.offsetHeight : element.offsetWidth;
|
|
47
|
+
}
|
|
48
|
+
function axisStart(element) {
|
|
49
|
+
return opts.vertical ? element.offsetTop : element.offsetLeft;
|
|
50
|
+
}
|
|
51
|
+
function applyStyles() {
|
|
52
|
+
if (!opts.manageStyles) return;
|
|
53
|
+
lane.style.overflow = "hidden";
|
|
54
|
+
lane.style.touchAction = opts.vertical ? "pan-x" : "pan-y";
|
|
55
|
+
lane.style.userSelect = "none";
|
|
56
|
+
lane.style.webkitUserSelect = "none";
|
|
57
|
+
track.style.willChange = "transform";
|
|
58
|
+
}
|
|
59
|
+
function render() {
|
|
60
|
+
const x = opts.vertical ? 0 : -offset;
|
|
61
|
+
const y = opts.vertical ? -offset : 0;
|
|
62
|
+
track.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
|
63
|
+
}
|
|
64
|
+
function readPeriod() {
|
|
65
|
+
const first = track.children[0];
|
|
66
|
+
const second = track.children[1];
|
|
67
|
+
if (!first) return 0;
|
|
68
|
+
if (second) {
|
|
69
|
+
const step = axisStart(second) - axisStart(first);
|
|
70
|
+
if (step > 0) return step;
|
|
71
|
+
}
|
|
72
|
+
return axisSize(first);
|
|
73
|
+
}
|
|
74
|
+
function requiredCopies(measured) {
|
|
75
|
+
if (measured <= 0) return 2;
|
|
76
|
+
return Math.max(2, Math.ceil(axisSize(lane) / measured) + 1);
|
|
77
|
+
}
|
|
78
|
+
function syncClones(count) {
|
|
79
|
+
const source = track.children[0];
|
|
80
|
+
if (!source) return;
|
|
81
|
+
while (track.children.length > count) track.removeChild(track.lastElementChild);
|
|
82
|
+
while (track.children.length < count) {
|
|
83
|
+
const clone = source.cloneNode(true);
|
|
84
|
+
clone.setAttribute("aria-hidden", "true");
|
|
85
|
+
clone.dataset.enigmaMarqueeCopy = String(track.children.length);
|
|
86
|
+
track.appendChild(clone);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function measure() {
|
|
90
|
+
if (destroyed) return;
|
|
91
|
+
const cloning = opts.copies === "clone";
|
|
92
|
+
if (cloning && track.children.length < 2) syncClones(2);
|
|
93
|
+
let measured = readPeriod();
|
|
94
|
+
let needed = requiredCopies(measured);
|
|
95
|
+
if (cloning && track.children.length !== needed) {
|
|
96
|
+
syncClones(needed);
|
|
97
|
+
measured = readPeriod();
|
|
98
|
+
needed = requiredCopies(measured);
|
|
99
|
+
}
|
|
100
|
+
const changed = measured !== period || needed !== copyCount;
|
|
101
|
+
period = measured;
|
|
102
|
+
if (needed !== copyCount) {
|
|
103
|
+
copyCount = needed;
|
|
104
|
+
opts.onCopyCountChange?.(needed);
|
|
105
|
+
}
|
|
106
|
+
if (period > 0) offset = wrap(offset);
|
|
107
|
+
render();
|
|
108
|
+
if (changed) opts.onMeasure?.(period, copyCount);
|
|
109
|
+
start();
|
|
110
|
+
}
|
|
111
|
+
function wrap(value) {
|
|
112
|
+
if (period <= 0) return value;
|
|
113
|
+
return (value % period + period) % period;
|
|
114
|
+
}
|
|
115
|
+
function tick(now) {
|
|
116
|
+
frame = 0;
|
|
117
|
+
if (destroyed) return;
|
|
118
|
+
const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
|
|
119
|
+
lastTime = now;
|
|
120
|
+
if (!dragging) {
|
|
121
|
+
velocity += (target() - velocity) * (1 - Math.pow(opts.decay, dt));
|
|
122
|
+
offset = wrap(offset + velocity * dt);
|
|
123
|
+
}
|
|
124
|
+
render();
|
|
125
|
+
const idle = !dragging && Math.abs(velocity) < IDLE_EPSILON && Math.abs(target()) < IDLE_EPSILON;
|
|
126
|
+
if (idle) {
|
|
127
|
+
velocity = 0;
|
|
128
|
+
running = false;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
frame = requestAnimationFrame(tick);
|
|
132
|
+
}
|
|
133
|
+
function start() {
|
|
134
|
+
if (destroyed || running) return;
|
|
135
|
+
running = true;
|
|
136
|
+
lastTime = performance.now();
|
|
137
|
+
frame = requestAnimationFrame(tick);
|
|
138
|
+
}
|
|
139
|
+
function pointerDelta(event) {
|
|
140
|
+
return opts.vertical ? event.clientY : event.clientX;
|
|
141
|
+
}
|
|
142
|
+
function onPointerDown(event) {
|
|
143
|
+
if (event.pointerType !== "mouse") noteTouch();
|
|
144
|
+
if (!opts.draggable || event.button > 0 || pointerId !== null) return;
|
|
145
|
+
pointerId = event.pointerId;
|
|
146
|
+
dragging = true;
|
|
147
|
+
lastPointer = pointerDelta(event);
|
|
148
|
+
startPointer = lastPointer;
|
|
149
|
+
lastMoveTime = event.timeStamp;
|
|
150
|
+
maxTravel = 0;
|
|
151
|
+
lane.dataset.dragging = "true";
|
|
152
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
153
|
+
window.addEventListener("pointerup", onPointerUp);
|
|
154
|
+
window.addEventListener("pointercancel", onPointerUp);
|
|
155
|
+
start();
|
|
156
|
+
}
|
|
157
|
+
function onPointerMove(event) {
|
|
158
|
+
if (event.pointerId !== pointerId) return;
|
|
159
|
+
const position = pointerDelta(event);
|
|
160
|
+
const delta = position - lastPointer;
|
|
161
|
+
lastPointer = position;
|
|
162
|
+
const dt = Math.max((event.timeStamp - lastMoveTime) / 1e3, 1e-3);
|
|
163
|
+
lastMoveTime = event.timeStamp;
|
|
164
|
+
maxTravel = Math.max(maxTravel, Math.abs(position - startPointer));
|
|
165
|
+
offset = wrap(offset - delta);
|
|
166
|
+
const instant = -delta / dt;
|
|
167
|
+
velocity = velocity * VELOCITY_SMOOTHING + instant * (1 - VELOCITY_SMOOTHING);
|
|
168
|
+
}
|
|
169
|
+
function onPointerUp(event) {
|
|
170
|
+
if (event.pointerId !== pointerId) return;
|
|
171
|
+
pointerId = null;
|
|
172
|
+
dragging = false;
|
|
173
|
+
delete lane.dataset.dragging;
|
|
174
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
175
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
176
|
+
window.removeEventListener("pointercancel", onPointerUp);
|
|
177
|
+
if (event.timeStamp - lastMoveTime > STILL_MS) velocity = 0;
|
|
178
|
+
start();
|
|
179
|
+
}
|
|
180
|
+
function onClickCapture(event) {
|
|
181
|
+
if (maxTravel <= DRAG_THRESHOLD) return;
|
|
182
|
+
event.preventDefault();
|
|
183
|
+
event.stopPropagation();
|
|
184
|
+
}
|
|
185
|
+
function onClick(event) {
|
|
186
|
+
if (maxTravel > DRAG_THRESHOLD) return;
|
|
187
|
+
opts.onItemClick?.(event);
|
|
188
|
+
}
|
|
189
|
+
function onPointerEnter(event) {
|
|
190
|
+
if (event.pointerType !== "mouse") {
|
|
191
|
+
noteTouch();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (Date.now() - lastTouchAt < TOUCH_HOVER_SUPPRESS_MS) return;
|
|
195
|
+
hovering = true;
|
|
196
|
+
lane.dataset.hovering = "true";
|
|
197
|
+
start();
|
|
198
|
+
}
|
|
199
|
+
function onPointerLeave() {
|
|
200
|
+
hovering = false;
|
|
201
|
+
delete lane.dataset.hovering;
|
|
202
|
+
start();
|
|
203
|
+
}
|
|
204
|
+
function noteTouch() {
|
|
205
|
+
lastTouchAt = Date.now();
|
|
206
|
+
if (!hovering) return;
|
|
207
|
+
hovering = false;
|
|
208
|
+
delete lane.dataset.hovering;
|
|
209
|
+
start();
|
|
210
|
+
}
|
|
211
|
+
function onMotionChange(event) {
|
|
212
|
+
reducedMotion = event.matches;
|
|
213
|
+
lane.dataset.reducedMotion = String(reducedMotion);
|
|
214
|
+
start();
|
|
215
|
+
}
|
|
216
|
+
const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => measure()) : null;
|
|
217
|
+
function init() {
|
|
218
|
+
applyStyles();
|
|
219
|
+
lane.dataset.enigmaMarquee = "";
|
|
220
|
+
lane.dataset.reducedMotion = String(reducedMotion);
|
|
221
|
+
track.dataset.enigmaMarqueeTrack = "";
|
|
222
|
+
for (const child of Array.from(track.children)) {
|
|
223
|
+
child.dataset.enigmaMarqueeCopy ??= "0";
|
|
224
|
+
}
|
|
225
|
+
lane.addEventListener("pointerdown", onPointerDown);
|
|
226
|
+
lane.addEventListener("pointerenter", onPointerEnter);
|
|
227
|
+
lane.addEventListener("pointerleave", onPointerLeave);
|
|
228
|
+
lane.addEventListener("touchstart", noteTouch, { passive: true });
|
|
229
|
+
lane.addEventListener("click", onClickCapture, true);
|
|
230
|
+
lane.addEventListener("click", onClick);
|
|
231
|
+
motionQuery?.addEventListener("change", onMotionChange);
|
|
232
|
+
resizeObserver?.observe(lane);
|
|
233
|
+
const first = track.children[0];
|
|
234
|
+
if (first) resizeObserver?.observe(first);
|
|
235
|
+
measure();
|
|
236
|
+
void document.fonts?.ready.then(() => measure());
|
|
237
|
+
}
|
|
238
|
+
init();
|
|
239
|
+
return {
|
|
240
|
+
get offset() {
|
|
241
|
+
return offset;
|
|
242
|
+
},
|
|
243
|
+
get period() {
|
|
244
|
+
return period;
|
|
245
|
+
},
|
|
246
|
+
get copyCount() {
|
|
247
|
+
return copyCount;
|
|
248
|
+
},
|
|
249
|
+
get dragging() {
|
|
250
|
+
return dragging;
|
|
251
|
+
},
|
|
252
|
+
get reducedMotion() {
|
|
253
|
+
return reducedMotion;
|
|
254
|
+
},
|
|
255
|
+
update(next) {
|
|
256
|
+
const styleKeys = next.vertical !== void 0 || next.manageStyles !== void 0;
|
|
257
|
+
opts = { ...opts, ...next };
|
|
258
|
+
if (styleKeys) applyStyles();
|
|
259
|
+
measure();
|
|
260
|
+
},
|
|
261
|
+
measure,
|
|
262
|
+
pause() {
|
|
263
|
+
paused = true;
|
|
264
|
+
start();
|
|
265
|
+
},
|
|
266
|
+
resume() {
|
|
267
|
+
paused = false;
|
|
268
|
+
start();
|
|
269
|
+
},
|
|
270
|
+
destroy() {
|
|
271
|
+
destroyed = true;
|
|
272
|
+
if (frame) cancelAnimationFrame(frame);
|
|
273
|
+
resizeObserver?.disconnect();
|
|
274
|
+
motionQuery?.removeEventListener("change", onMotionChange);
|
|
275
|
+
lane.removeEventListener("pointerdown", onPointerDown);
|
|
276
|
+
lane.removeEventListener("pointerenter", onPointerEnter);
|
|
277
|
+
lane.removeEventListener("pointerleave", onPointerLeave);
|
|
278
|
+
lane.removeEventListener("touchstart", noteTouch);
|
|
279
|
+
lane.removeEventListener("click", onClickCapture, true);
|
|
280
|
+
lane.removeEventListener("click", onClick);
|
|
281
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
282
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
283
|
+
window.removeEventListener("pointercancel", onPointerUp);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/core/input.ts
|
|
289
|
+
var EYE = '<svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>';
|
|
290
|
+
var EYE_OFF = '<svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.6 5.2A9.8 9.8 0 0 1 12 5c6.5 0 10 7 10 7a17.6 17.6 0 0 1-3.2 4.2M6.2 6.2A17.7 17.7 0 0 0 2 12s3.5 7 10 7a9.6 9.6 0 0 0 4.2-.9"/><path d="m2 2 20 20"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/></svg>';
|
|
291
|
+
function resolve(value, state) {
|
|
292
|
+
return typeof value === "function" ? value(state) : value;
|
|
293
|
+
}
|
|
294
|
+
function paint(target, icon) {
|
|
295
|
+
if (typeof icon === "string") target.innerHTML = icon;
|
|
296
|
+
else {
|
|
297
|
+
target.replaceChildren(icon);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function createInput(input, options = {}) {
|
|
301
|
+
let opts = { ...options };
|
|
302
|
+
let revealed = false;
|
|
303
|
+
let destroyed = false;
|
|
304
|
+
let restoreTimer = null;
|
|
305
|
+
const owned = !opts.container;
|
|
306
|
+
const container = opts.container ?? document.createElement("span");
|
|
307
|
+
const buttons = /* @__PURE__ */ new Map();
|
|
308
|
+
function state() {
|
|
309
|
+
return { input, revealed, locked: input.disabled || input.readOnly };
|
|
310
|
+
}
|
|
311
|
+
function revealConfig() {
|
|
312
|
+
return typeof opts.reveal === "object" ? opts.reveal : {};
|
|
313
|
+
}
|
|
314
|
+
function revealAction() {
|
|
315
|
+
const config = revealConfig();
|
|
316
|
+
return {
|
|
317
|
+
name: "reveal",
|
|
318
|
+
label: (current) => current.revealed ? config.hideLabel ?? "Hide password" : config.showLabel ?? "Show password",
|
|
319
|
+
icon: (current) => current.revealed ? config.hideIcon ?? EYE_OFF : config.showIcon ?? EYE,
|
|
320
|
+
pressed: (current) => current.revealed,
|
|
321
|
+
onSelect: () => setRevealed(!revealed)
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function isPasswordField() {
|
|
325
|
+
return input.type === "password" || revealed && input.dataset.enigmaInputReveal === "on";
|
|
326
|
+
}
|
|
327
|
+
function activeActions() {
|
|
328
|
+
const custom = opts.actions ?? [];
|
|
329
|
+
const wantsReveal = opts.reveal !== false && isPasswordField();
|
|
330
|
+
const builtIn = wantsReveal && !custom.some((action) => action.name === "reveal") ? [revealAction()] : [];
|
|
331
|
+
return [...builtIn, ...custom];
|
|
332
|
+
}
|
|
333
|
+
function setRevealed(next) {
|
|
334
|
+
if (!isPasswordField() && next) return;
|
|
335
|
+
const selection = readSelection();
|
|
336
|
+
revealed = next;
|
|
337
|
+
input.type = next ? "text" : "password";
|
|
338
|
+
input.dataset.enigmaInputReveal = next ? "on" : "off";
|
|
339
|
+
render();
|
|
340
|
+
if (selection) {
|
|
341
|
+
applySelection(selection);
|
|
342
|
+
if (restoreTimer !== null) clearTimeout(restoreTimer);
|
|
343
|
+
restoreTimer = setTimeout(() => {
|
|
344
|
+
restoreTimer = null;
|
|
345
|
+
if (!destroyed && document.activeElement === input) applySelection(selection);
|
|
346
|
+
}, 0);
|
|
347
|
+
}
|
|
348
|
+
opts.onRevealChange?.(next);
|
|
349
|
+
}
|
|
350
|
+
function readSelection() {
|
|
351
|
+
if (document.activeElement !== input) return null;
|
|
352
|
+
try {
|
|
353
|
+
const { selectionStart, selectionEnd } = input;
|
|
354
|
+
return selectionStart === null || selectionEnd === null ? null : [selectionStart, selectionEnd];
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function applySelection(selection) {
|
|
360
|
+
try {
|
|
361
|
+
input.setSelectionRange(selection[0], selection[1]);
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function render() {
|
|
366
|
+
if (destroyed) return;
|
|
367
|
+
const selection = readSelection();
|
|
368
|
+
const current = state();
|
|
369
|
+
const actions = activeActions();
|
|
370
|
+
const seen = /* @__PURE__ */ new Set();
|
|
371
|
+
for (const action of actions) {
|
|
372
|
+
seen.add(action.name);
|
|
373
|
+
const visible = action.visible ? action.visible(current) : true;
|
|
374
|
+
let button = buttons.get(action.name);
|
|
375
|
+
if (!button) {
|
|
376
|
+
button = document.createElement("button");
|
|
377
|
+
button.type = "button";
|
|
378
|
+
button.dataset.enigmaInputAction = action.name;
|
|
379
|
+
buttons.set(action.name, button);
|
|
380
|
+
container.append(button);
|
|
381
|
+
}
|
|
382
|
+
if (!visible) {
|
|
383
|
+
button.remove();
|
|
384
|
+
buttons.delete(action.name);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (!button.isConnected) container.append(button);
|
|
388
|
+
const label = resolve(action.label, current);
|
|
389
|
+
button.setAttribute("aria-label", label);
|
|
390
|
+
button.title = label;
|
|
391
|
+
button.disabled = current.locked;
|
|
392
|
+
if (action.pressed) button.setAttribute("aria-pressed", String(action.pressed(current)));
|
|
393
|
+
else button.removeAttribute("aria-pressed");
|
|
394
|
+
paint(button, resolve(action.icon, current));
|
|
395
|
+
}
|
|
396
|
+
for (const [name, button] of [...buttons]) {
|
|
397
|
+
if (seen.has(name)) continue;
|
|
398
|
+
button.remove();
|
|
399
|
+
buttons.delete(name);
|
|
400
|
+
}
|
|
401
|
+
container.hidden = buttons.size === 0;
|
|
402
|
+
if (selection) {
|
|
403
|
+
try {
|
|
404
|
+
input.setSelectionRange(selection[0], selection[1]);
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function onMouseDown(event) {
|
|
410
|
+
if (!event.target?.closest("[data-enigma-input-action]")) return;
|
|
411
|
+
event.preventDefault();
|
|
412
|
+
}
|
|
413
|
+
function onClick(event) {
|
|
414
|
+
const target = event.target?.closest("[data-enigma-input-action]");
|
|
415
|
+
if (!target) return;
|
|
416
|
+
const action = activeActions().find((candidate) => candidate.name === target.dataset.enigmaInputAction);
|
|
417
|
+
if (!action) return;
|
|
418
|
+
event.preventDefault();
|
|
419
|
+
action.onSelect(state());
|
|
420
|
+
}
|
|
421
|
+
const observer = typeof MutationObserver === "function" ? new MutationObserver(() => {
|
|
422
|
+
if (!isPasswordField() && revealed) revealed = false;
|
|
423
|
+
render();
|
|
424
|
+
}) : null;
|
|
425
|
+
function mount() {
|
|
426
|
+
input.dataset.enigmaInput = "";
|
|
427
|
+
container.dataset.enigmaInputActions = "";
|
|
428
|
+
container.dataset.position = opts.position ?? "end";
|
|
429
|
+
if (owned && !container.isConnected) {
|
|
430
|
+
if ((opts.position ?? "end") === "start") input.before(container);
|
|
431
|
+
else input.after(container);
|
|
432
|
+
}
|
|
433
|
+
container.addEventListener("mousedown", onMouseDown);
|
|
434
|
+
container.addEventListener("click", onClick);
|
|
435
|
+
observer?.observe(input, { attributes: true, attributeFilter: ["type", "disabled", "readonly"] });
|
|
436
|
+
render();
|
|
437
|
+
}
|
|
438
|
+
mount();
|
|
439
|
+
return {
|
|
440
|
+
get revealed() {
|
|
441
|
+
return revealed;
|
|
442
|
+
},
|
|
443
|
+
reveal(next) {
|
|
444
|
+
setRevealed(next ?? !revealed);
|
|
445
|
+
},
|
|
446
|
+
refresh: render,
|
|
447
|
+
update(next) {
|
|
448
|
+
const moved = next.position !== void 0 && next.position !== opts.position;
|
|
449
|
+
opts = { ...opts, ...next };
|
|
450
|
+
if (moved && owned) {
|
|
451
|
+
container.dataset.position = opts.position ?? "end";
|
|
452
|
+
if (opts.position === "start") input.before(container);
|
|
453
|
+
else input.after(container);
|
|
454
|
+
}
|
|
455
|
+
render();
|
|
456
|
+
},
|
|
457
|
+
destroy() {
|
|
458
|
+
destroyed = true;
|
|
459
|
+
if (restoreTimer !== null) clearTimeout(restoreTimer);
|
|
460
|
+
observer?.disconnect();
|
|
461
|
+
container.removeEventListener("mousedown", onMouseDown);
|
|
462
|
+
container.removeEventListener("click", onClick);
|
|
463
|
+
for (const button of buttons.values()) button.remove();
|
|
464
|
+
buttons.clear();
|
|
465
|
+
if (owned) container.remove();
|
|
466
|
+
delete input.dataset.enigmaInput;
|
|
467
|
+
delete input.dataset.enigmaInputReveal;
|
|
468
|
+
if (revealed) input.type = "password";
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/core/search.ts
|
|
474
|
+
var DEFAULT_DEBOUNCE = 120;
|
|
475
|
+
function fold(value) {
|
|
476
|
+
return value.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
|
|
477
|
+
}
|
|
478
|
+
function readPath(item, path) {
|
|
479
|
+
let cursor = item;
|
|
480
|
+
for (const step of path.split(".")) {
|
|
481
|
+
if (cursor == null || typeof cursor !== "object") return "";
|
|
482
|
+
cursor = cursor[step];
|
|
483
|
+
}
|
|
484
|
+
return cursor == null ? "" : String(cursor);
|
|
485
|
+
}
|
|
486
|
+
function substringMatcher(query, items, keys) {
|
|
487
|
+
const needle = fold(query);
|
|
488
|
+
const results = [];
|
|
489
|
+
for (const item of items) {
|
|
490
|
+
const fields = keys.length ? keys : [""];
|
|
491
|
+
let best = null;
|
|
492
|
+
for (let index = 0; index < fields.length; index++) {
|
|
493
|
+
const key = fields[index];
|
|
494
|
+
const haystack = fold(key ? readPath(item, key) : String(item));
|
|
495
|
+
const at = haystack.indexOf(needle);
|
|
496
|
+
if (at < 0) continue;
|
|
497
|
+
const score = haystack === needle ? 0 : (at + 1) / (haystack.length + 1) + index * 0.01;
|
|
498
|
+
if (!best || score < best.score) best = { item, score, key: key || void 0 };
|
|
499
|
+
}
|
|
500
|
+
if (best) results.push(best);
|
|
501
|
+
}
|
|
502
|
+
return results.sort((left, right) => left.score - right.score);
|
|
503
|
+
}
|
|
504
|
+
function createSearch(options = {}) {
|
|
505
|
+
let opts = { ...options };
|
|
506
|
+
let items = opts.items ?? [];
|
|
507
|
+
let query = "";
|
|
508
|
+
let results = [];
|
|
509
|
+
let timer = null;
|
|
510
|
+
let engine = null;
|
|
511
|
+
let destroyed = false;
|
|
512
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
513
|
+
function buildEngine() {
|
|
514
|
+
engine = null;
|
|
515
|
+
if (opts.matcher || !opts.fuse) return;
|
|
516
|
+
engine = new opts.fuse(items, { keys: opts.keys ?? [], includeScore: true, includeMatches: true, threshold: 0.4, ...opts.fuseOptions });
|
|
517
|
+
}
|
|
518
|
+
function run(next) {
|
|
519
|
+
const trimmed = next.trim();
|
|
520
|
+
const minLength = opts.minLength ?? 1;
|
|
521
|
+
let found;
|
|
522
|
+
if (trimmed.length < minLength) {
|
|
523
|
+
found = (opts.empty ?? "none") === "all" ? items.map((item) => ({ item, score: 0 })) : [];
|
|
524
|
+
} else if (opts.matcher) {
|
|
525
|
+
found = opts.matcher(trimmed, items);
|
|
526
|
+
} else if (engine) {
|
|
527
|
+
found = engine.search(trimmed).map((hit) => ({
|
|
528
|
+
item: hit.item,
|
|
529
|
+
score: hit.score ?? 0,
|
|
530
|
+
key: hit.matches?.[0]?.key
|
|
531
|
+
}));
|
|
532
|
+
} else {
|
|
533
|
+
found = substringMatcher(trimmed, items, opts.keys ?? []);
|
|
534
|
+
}
|
|
535
|
+
return opts.limit != null ? found.slice(0, opts.limit) : found;
|
|
536
|
+
}
|
|
537
|
+
function publish(next) {
|
|
538
|
+
query = next;
|
|
539
|
+
results = run(next);
|
|
540
|
+
opts.onResults?.(results, query);
|
|
541
|
+
for (const listener of listeners) listener(results, query);
|
|
542
|
+
return results;
|
|
543
|
+
}
|
|
544
|
+
function clearTimer() {
|
|
545
|
+
if (timer === null) return;
|
|
546
|
+
clearTimeout(timer);
|
|
547
|
+
timer = null;
|
|
548
|
+
}
|
|
549
|
+
buildEngine();
|
|
550
|
+
return {
|
|
551
|
+
get query() {
|
|
552
|
+
return query;
|
|
553
|
+
},
|
|
554
|
+
get results() {
|
|
555
|
+
return results;
|
|
556
|
+
},
|
|
557
|
+
search(next) {
|
|
558
|
+
if (destroyed) return;
|
|
559
|
+
clearTimer();
|
|
560
|
+
const wait = opts.debounce ?? DEFAULT_DEBOUNCE;
|
|
561
|
+
if (wait <= 0) {
|
|
562
|
+
publish(next);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
timer = setTimeout(() => {
|
|
566
|
+
timer = null;
|
|
567
|
+
publish(next);
|
|
568
|
+
}, wait);
|
|
569
|
+
},
|
|
570
|
+
searchNow(next) {
|
|
571
|
+
clearTimer();
|
|
572
|
+
return publish(next);
|
|
573
|
+
},
|
|
574
|
+
setItems(next) {
|
|
575
|
+
items = next;
|
|
576
|
+
buildEngine();
|
|
577
|
+
if (query) publish(query);
|
|
578
|
+
},
|
|
579
|
+
update(next) {
|
|
580
|
+
opts = { ...opts, ...next };
|
|
581
|
+
if (next.items) items = next.items;
|
|
582
|
+
buildEngine();
|
|
583
|
+
if (query) publish(query);
|
|
584
|
+
},
|
|
585
|
+
attach(input) {
|
|
586
|
+
const onInput = () => this.search(input.value);
|
|
587
|
+
const onKeyDown = (event) => {
|
|
588
|
+
if (event.key !== "Escape" || !input.value) return;
|
|
589
|
+
event.stopPropagation();
|
|
590
|
+
input.value = "";
|
|
591
|
+
this.searchNow("");
|
|
592
|
+
};
|
|
593
|
+
input.addEventListener("input", onInput);
|
|
594
|
+
input.addEventListener("keydown", onKeyDown);
|
|
595
|
+
input.dataset.enigmaSearch = "";
|
|
596
|
+
return () => {
|
|
597
|
+
input.removeEventListener("input", onInput);
|
|
598
|
+
input.removeEventListener("keydown", onKeyDown);
|
|
599
|
+
delete input.dataset.enigmaSearch;
|
|
600
|
+
};
|
|
601
|
+
},
|
|
602
|
+
subscribe(listener) {
|
|
603
|
+
listeners.add(listener);
|
|
604
|
+
return () => {
|
|
605
|
+
listeners.delete(listener);
|
|
606
|
+
};
|
|
607
|
+
},
|
|
608
|
+
destroy() {
|
|
609
|
+
destroyed = true;
|
|
610
|
+
clearTimer();
|
|
611
|
+
listeners.clear();
|
|
612
|
+
engine = null;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export { createInput, createMarquee, createSearch };
|