@kywi-software/js 0.6.7 → 0.7.1

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/kywi.js CHANGED
@@ -22,8 +22,11 @@ var Kywi = (() => {
22
22
  var src_exports = {};
23
23
  __export(src_exports, {
24
24
  Kywi: () => Kywi,
25
+ NAV_BREAKPOINT: () => NAV_BREAKPOINT,
25
26
  bootAudienceEngine: () => bootAudienceEngine,
26
27
  bootExperiments: () => bootExperiments,
28
+ enhanceNav: () => enhanceNav,
29
+ initNavMenus: () => initNavMenus,
27
30
  openTransparencyPanel: () => openTransparencyPanel,
28
31
  renderTransparencyPanel: () => renderTransparencyPanel
29
32
  });
@@ -319,6 +322,188 @@ var Kywi = (() => {
319
322
  }
320
323
  }
321
324
 
325
+ // src/nav/menu.ts
326
+ var NAV_BREAKPOINT = 860;
327
+ var CLOSE_DELAY_MS = 120;
328
+ var ITEM_CLASS = "kywi-nav-item--has-children";
329
+ var OPEN_CLASS = "kywi-nav-item--open";
330
+ var TOGGLE_CLASS = "kywi-nav-toggle";
331
+ var PANEL_CLASS = "kywi-nav-panel";
332
+ var FLIP_CLASS = "kywi-nav-panel--flip";
333
+ var READY_ATTRIBUTE = "data-kywi-nav-ready";
334
+ function directChild(parent, className) {
335
+ for (const child of Array.from(parent.children)) {
336
+ if (child instanceof HTMLElement && child.classList.contains(className)) return child;
337
+ }
338
+ return null;
339
+ }
340
+ function isDesktop() {
341
+ if (typeof window === "undefined") return false;
342
+ if (typeof window.matchMedia !== "function") return true;
343
+ return window.matchMedia(`(min-width: ${NAV_BREAKPOINT}px)`).matches;
344
+ }
345
+ function panelLinks(panel) {
346
+ return Array.from(panel.querySelectorAll("a[href], button:not([disabled])"));
347
+ }
348
+ function enhanceNav(nav) {
349
+ const cleanups = [];
350
+ const entries = [];
351
+ for (const item of Array.from(nav.querySelectorAll(`.${ITEM_CLASS}`))) {
352
+ const toggle = directChild(item, TOGGLE_CLASS);
353
+ const panel = directChild(item, PANEL_CLASS);
354
+ if (toggle && panel) entries.push({ item, toggle, panel });
355
+ }
356
+ if (entries.length === 0) return () => {
357
+ };
358
+ let closeTimer;
359
+ const cancelClose = () => {
360
+ if (closeTimer !== void 0) {
361
+ clearTimeout(closeTimer);
362
+ closeTimer = void 0;
363
+ }
364
+ };
365
+ function isOpen(entry) {
366
+ return entry.item.classList.contains(OPEN_CLASS);
367
+ }
368
+ function close(entry) {
369
+ entry.item.classList.remove(OPEN_CLASS);
370
+ entry.toggle.setAttribute("aria-expanded", "false");
371
+ entry.panel.classList.remove(FLIP_CLASS);
372
+ }
373
+ function closeAll(except) {
374
+ for (const entry of entries) {
375
+ if (except && (entry === except || entry.item.contains(except.item))) continue;
376
+ close(entry);
377
+ }
378
+ }
379
+ function applyEdgeFlip(entry) {
380
+ if (typeof entry.panel.getBoundingClientRect !== "function") return;
381
+ entry.panel.classList.remove(FLIP_CLASS);
382
+ const rect = entry.panel.getBoundingClientRect();
383
+ const viewport = window.innerWidth || document.documentElement.clientWidth || 0;
384
+ if (viewport > 0 && rect.width > 0 && rect.right > viewport - 4) {
385
+ entry.panel.classList.add(FLIP_CLASS);
386
+ }
387
+ }
388
+ function open(entry) {
389
+ cancelClose();
390
+ closeAll(entry);
391
+ entry.item.classList.add(OPEN_CLASS);
392
+ entry.toggle.setAttribute("aria-expanded", "true");
393
+ applyEdgeFlip(entry);
394
+ }
395
+ function on(target, type, handler, options) {
396
+ target.addEventListener(type, handler, options);
397
+ cleanups.push(() => target.removeEventListener(type, handler, options));
398
+ }
399
+ for (const entry of entries) {
400
+ on(entry.toggle, "click", (event) => {
401
+ event.preventDefault();
402
+ if (isOpen(entry)) {
403
+ close(entry);
404
+ } else {
405
+ open(entry);
406
+ }
407
+ });
408
+ on(entry.item, "mouseenter", () => {
409
+ if (!isDesktop()) return;
410
+ open(entry);
411
+ });
412
+ on(entry.item, "mouseleave", () => {
413
+ if (!isDesktop()) return;
414
+ cancelClose();
415
+ closeTimer = setTimeout(() => {
416
+ close(entry);
417
+ closeTimer = void 0;
418
+ }, CLOSE_DELAY_MS);
419
+ });
420
+ }
421
+ on(nav, "focusin", (event) => {
422
+ const target = event.target;
423
+ if (!target) return;
424
+ for (const entry of entries) {
425
+ if (entry.panel.contains(target)) {
426
+ open(entry);
427
+ return;
428
+ }
429
+ }
430
+ for (const entry of entries) {
431
+ if (!entry.item.contains(target)) close(entry);
432
+ }
433
+ });
434
+ on(nav, "keydown", (event) => {
435
+ const target = event.target;
436
+ if (!target) return;
437
+ const entry = entries.find((e) => e.item.contains(target));
438
+ if (!entry) return;
439
+ if (event.key === "Escape") {
440
+ const openEntry = entries.filter((e) => e.item.contains(target) && isOpen(e)).pop();
441
+ if (!openEntry) return;
442
+ event.preventDefault();
443
+ close(openEntry);
444
+ openEntry.toggle.focus();
445
+ return;
446
+ }
447
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
448
+ if (target === entry.toggle) {
449
+ if (event.key === "ArrowDown") {
450
+ event.preventDefault();
451
+ if (!isOpen(entry)) open(entry);
452
+ panelLinks(entry.panel)[0]?.focus();
453
+ } else if (isOpen(entry)) {
454
+ event.preventDefault();
455
+ close(entry);
456
+ }
457
+ return;
458
+ }
459
+ const owner = entries.find((e) => e.panel.contains(target));
460
+ if (!owner) return;
461
+ const links = panelLinks(owner.panel);
462
+ const index = links.indexOf(target);
463
+ if (index === -1) return;
464
+ event.preventDefault();
465
+ const next = event.key === "ArrowDown" ? links[(index + 1) % links.length] : links[(index - 1 + links.length) % links.length];
466
+ next?.focus();
467
+ });
468
+ on(document, "click", (event) => {
469
+ const target = event.target;
470
+ if (target && nav.contains(target)) return;
471
+ cancelClose();
472
+ closeAll();
473
+ });
474
+ if (typeof window.matchMedia === "function") {
475
+ const query = window.matchMedia(`(min-width: ${NAV_BREAKPOINT}px)`);
476
+ const onChange = () => {
477
+ cancelClose();
478
+ closeAll();
479
+ };
480
+ if (typeof query.addEventListener === "function") {
481
+ query.addEventListener("change", onChange);
482
+ cleanups.push(() => query.removeEventListener("change", onChange));
483
+ }
484
+ }
485
+ nav.setAttribute(READY_ATTRIBUTE, "");
486
+ return () => {
487
+ cancelClose();
488
+ for (const undo of cleanups) undo();
489
+ cleanups.length = 0;
490
+ closeAll();
491
+ nav.removeAttribute(READY_ATTRIBUTE);
492
+ };
493
+ }
494
+ function initNavMenus(root = document) {
495
+ if (typeof document === "undefined") return () => {
496
+ };
497
+ const teardowns = [];
498
+ for (const nav of Array.from(root.querySelectorAll("[data-kywi-nav]"))) {
499
+ if (nav.hasAttribute(READY_ATTRIBUTE)) continue;
500
+ teardowns.push(enhanceNav(nav));
501
+ }
502
+ return () => {
503
+ for (const undo of teardowns) undo();
504
+ };
505
+ }
506
+
322
507
  // src/experiments/index.ts
323
508
  function apiBaseFrom(ctx) {
324
509
  return ctx.basePath && ctx.basePath !== "/" ? `${ctx.basePath}/api/v1` : "/api/v1";
@@ -1526,6 +1711,7 @@ var Kywi = (() => {
1526
1711
  Kywi.renderFeed = renderFeed;
1527
1712
  initModules();
1528
1713
  initFormHooks();
1714
+ initNavMenus();
1529
1715
  bootExperiments({ context: Kywi.context });
1530
1716
  booted = true;
1531
1717
  }