@avenue-ticketing/ui 0.8.0 → 0.10.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.
@@ -200,6 +200,85 @@ var Button = React5.forwardRef(
200
200
  }
201
201
  );
202
202
  Button.displayName = "Button";
203
+
204
+ // src/lib/typeahead.ts
205
+ var TYPEAHEAD_TIMEOUT_MS = 500;
206
+ function createTypeaheadState() {
207
+ return { search: "", timer: null };
208
+ }
209
+ function resetTypeahead(state) {
210
+ state.search = "";
211
+ if (state.timer) {
212
+ clearTimeout(state.timer);
213
+ state.timer = null;
214
+ }
215
+ }
216
+ function getItemLabel(item) {
217
+ const aria = item.getAttribute("aria-label")?.trim();
218
+ if (aria) return aria;
219
+ const marked = item.querySelector("[data-menu-label]");
220
+ if (marked?.textContent) return marked.textContent.replace(/\s+/g, " ").trim();
221
+ return (item.textContent ?? "").replace(/\s+/g, " ").trim();
222
+ }
223
+ function normalizeSearch(value) {
224
+ return value.trim().toLocaleLowerCase();
225
+ }
226
+ function isTypeaheadTarget(target) {
227
+ if (!(target instanceof HTMLElement)) return true;
228
+ if (target.isContentEditable) return false;
229
+ const tag = target.tagName;
230
+ return tag !== "INPUT" && tag !== "TEXTAREA" && tag !== "SELECT";
231
+ }
232
+ function handleTypeaheadKeyDown(event, items, state, options) {
233
+ if (options?.enabled === false || items.length === 0) return false;
234
+ if (event.ctrlKey || event.metaKey || event.altKey) return false;
235
+ if (event.key.length !== 1 || !isTypeaheadTarget(event.target)) return false;
236
+ event.preventDefault();
237
+ const timeoutMs = options?.timeoutMs ?? TYPEAHEAD_TIMEOUT_MS;
238
+ const char = event.key;
239
+ const prevSearch = state.search;
240
+ const repeatSingleChar = prevSearch.length === 1 && prevSearch === char;
241
+ if (repeatSingleChar) {
242
+ state.search = prevSearch;
243
+ } else {
244
+ state.search = prevSearch + char;
245
+ }
246
+ if (state.timer) clearTimeout(state.timer);
247
+ state.timer = setTimeout(() => {
248
+ state.search = "";
249
+ state.timer = null;
250
+ }, timeoutMs);
251
+ const labels = items.map((item) => normalizeSearch(getItemLabel(item)));
252
+ let needle = normalizeSearch(state.search);
253
+ let matches = items.map((item, index) => ({ item, index, label: labels[index] })).filter(({ label }) => label.startsWith(needle));
254
+ if (matches.length === 0 && state.search.length > 1) {
255
+ state.search = char;
256
+ needle = normalizeSearch(char);
257
+ matches = items.map((item, index) => ({ item, index, label: labels[index] })).filter(({ label }) => label.startsWith(needle));
258
+ }
259
+ if (matches.length === 0) return true;
260
+ const focused = document.activeElement;
261
+ const focusedIndex = focused ? items.indexOf(focused) : -1;
262
+ if (repeatSingleChar && focusedIndex !== -1) {
263
+ const currentMatch = matches.findIndex(
264
+ ({ index }) => index === focusedIndex
265
+ );
266
+ if (currentMatch !== -1) {
267
+ const next = matches[(currentMatch + 1) % matches.length];
268
+ next?.item.focus();
269
+ return true;
270
+ }
271
+ }
272
+ if (focusedIndex !== -1) {
273
+ const nextAfterFocus = matches.find(({ index }) => index > focusedIndex);
274
+ if (nextAfterFocus) {
275
+ nextAfterFocus.item.focus();
276
+ return true;
277
+ }
278
+ }
279
+ matches[0]?.item.focus();
280
+ return true;
281
+ }
203
282
  var DROPDOWN_PANEL_OPEN_EASING = "cubic-bezier(0,0.55,0.45,1)";
204
283
  var DROPDOWN_PANEL_CLOSE_EASING = "cubic-bezier(0.55,0,1,0.45)";
205
284
  var DROPDOWN_MENU_MIN_WIDTH_PX = 192;
@@ -316,9 +395,6 @@ function computePos(trigger, menu, side, align, offset, pad) {
316
395
  left = fl;
317
396
  }
318
397
  }
319
- const layoutHeight = maxHeight ?? contentHeight;
320
- left = Math.max(sx + pad, Math.min(left, vw + sx - mr.width - pad));
321
- top = Math.max(sy + pad, Math.min(top, vh + sy - layoutHeight - pad));
322
398
  return { top, left, side: effectiveSide, maxHeight };
323
399
  }
324
400
  function useIsMobile(breakpoint = 1025) {
@@ -494,7 +570,7 @@ function DropdownMobileBottomSheetPortal({
494
570
  "div",
495
571
  {
496
572
  className: cn(
497
- "fixed inset-0 bg-black/40 dark:bg-black/60",
573
+ "fixed inset-0 bg-black/40 dark:bg-primary/4",
498
574
  isAnimating ? "opacity-100" : "opacity-0"
499
575
  ),
500
576
  style: {
@@ -576,6 +652,7 @@ var DropdownContent = ({
576
652
  closeOnEscape = true,
577
653
  minWidth = "trigger",
578
654
  loop = true,
655
+ typeahead = true,
579
656
  mobileOptions,
580
657
  slideEntrance = true,
581
658
  slideEntranceOffsetPx: slideEntranceOffsetPxProp,
@@ -590,6 +667,7 @@ var DropdownContent = ({
590
667
  const [pos, setPos] = useState({ top: -9999, left: -9999, side });
591
668
  const [triggerW, setTriggerW] = useState(0);
592
669
  const menuRef = useRef(null);
670
+ const typeaheadStateRef = useRef(createTypeaheadState());
593
671
  const resolvedMobile = resolveDropdownMobileSheet(mobileOptions);
594
672
  const isMobileSheet = isMobile && resolvedMobile.sheet;
595
673
  const slideOffsetPx = slideEntranceOffsetPxProp ?? DROPDOWN_MOBILE_SHEET_SLIDE_ENTRANCE_OFFSET_DEFAULT_PX;
@@ -664,6 +742,9 @@ var DropdownContent = ({
664
742
  menuRef.current.focus();
665
743
  }
666
744
  }, [isAnimating]);
745
+ useEffect(() => {
746
+ if (!open) resetTypeahead(typeaheadStateRef.current);
747
+ }, [open]);
667
748
  useEffect(() => {
668
749
  if (!open) return;
669
750
  const handler = (e) => {
@@ -718,11 +799,16 @@ var DropdownContent = ({
718
799
  case "Tab":
719
800
  setOpen(false);
720
801
  break;
802
+ default:
803
+ handleTypeaheadKeyDown(e, items, typeaheadStateRef.current, {
804
+ enabled: typeahead
805
+ });
806
+ break;
721
807
  }
722
808
  };
723
809
  window.addEventListener("keydown", handler);
724
810
  return () => window.removeEventListener("keydown", handler);
725
- }, [open, closeOnEscape, loop, setOpen, triggerRef]);
811
+ }, [open, closeOnEscape, loop, typeahead, setOpen, triggerRef]);
726
812
  useEffect(() => {
727
813
  if (!open || !isMobileSheet) return;
728
814
  document.body.style.overflow = "hidden";
@@ -846,7 +932,7 @@ var DropdownItem = ({
846
932
  ),
847
933
  children: [
848
934
  icon && /* @__PURE__ */ jsx("span", { className: "flex size-4 shrink-0 items-center justify-center", children: icon }),
849
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1", children }),
935
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1", "data-menu-label": true, children }),
850
936
  shortcut ? /* @__PURE__ */ jsx(
851
937
  "span",
852
938
  {