@codbex/harmonia 1.11.0 → 1.12.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/harmonia.js CHANGED
@@ -4864,13 +4864,13 @@
4864
4864
  // src/components/menu.js
4865
4865
  function menu_default(Alpine) {
4866
4866
  Alpine.directive("h-menu-trigger", (el, { modifiers }) => {
4867
- el._menu_trigger = {
4867
+ el._h_menu_trigger = {
4868
4868
  isDropdown: modifiers.includes("dropdown"),
4869
4869
  setOpen(open) {
4870
4870
  el.setAttribute("aria-expanded", open);
4871
4871
  }
4872
4872
  };
4873
- if (el._menu_trigger.isDropdown) {
4873
+ if (el._h_menu_trigger.isDropdown) {
4874
4874
  el.setAttribute("aria-haspopup", "true");
4875
4875
  el.setAttribute("aria-expanded", "false");
4876
4876
  if (!el.hasAttribute("id")) {
@@ -4912,10 +4912,10 @@
4912
4912
  const menuTrigger = (() => {
4913
4913
  if (isSubmenu) return;
4914
4914
  let sibling = el.previousElementSibling;
4915
- while (sibling && !Object.prototype.hasOwnProperty.call(sibling, "_menu_trigger")) {
4915
+ while (sibling && !Object.prototype.hasOwnProperty.call(sibling, "_h_menu_trigger")) {
4916
4916
  sibling = sibling.previousElementSibling;
4917
4917
  }
4918
- if (!Object.prototype.hasOwnProperty.call(sibling, "_menu_trigger")) {
4918
+ if (!Object.prototype.hasOwnProperty.call(sibling, "_h_menu_trigger")) {
4919
4919
  throw new Error(`${original} menu must be placed after a menu trigger element`);
4920
4920
  }
4921
4921
  return sibling;
@@ -4937,16 +4937,22 @@
4937
4937
  }
4938
4938
  setAriaAttrubutes(menuSubItem);
4939
4939
  } else if (menuTrigger) {
4940
- setAriaAttrubutes(menuTrigger._menu_trigger.isDropdown ? menuTrigger : void 0);
4940
+ if (menuTrigger._h_menu_trigger.isDropdown) {
4941
+ if (!el.hasAttribute("id")) {
4942
+ el.setAttribute("id", `m${uuid_default()}`);
4943
+ }
4944
+ menuTrigger.setAttribute("aria-controls", el.getAttribute("id"));
4945
+ setAriaAttrubutes(menuTrigger);
4946
+ } else setAriaAttrubutes();
4941
4947
  } else {
4942
4948
  setAriaAttrubutes();
4943
4949
  }
4944
4950
  function listenForTrigger(listen) {
4945
4951
  if (listen) {
4946
- if (menuTrigger._menu_trigger.isDropdown) menuTrigger.addEventListener("click", openDropdown);
4952
+ if (menuTrigger._h_menu_trigger.isDropdown) menuTrigger.addEventListener("click", openDropdown);
4947
4953
  else menuTrigger.addEventListener("contextmenu", onContextmenu);
4948
4954
  } else {
4949
- if (menuTrigger._menu_trigger.isDropdown) menuTrigger.removeEventListener("click", openDropdown);
4955
+ if (menuTrigger._h_menu_trigger.isDropdown) menuTrigger.removeEventListener("click", openDropdown);
4950
4956
  else menuTrigger.removeEventListener("contextmenu", onContextmenu);
4951
4957
  }
4952
4958
  }
@@ -4975,8 +4981,8 @@
4975
4981
  } else {
4976
4982
  listenForTrigger(true);
4977
4983
  if (focusTrigger) menuTrigger.focus();
4978
- if (menuTrigger._menu_trigger.isDropdown) {
4979
- menuTrigger._menu_trigger.setOpen(false);
4984
+ if (menuTrigger._h_menu_trigger.isDropdown) {
4985
+ menuTrigger._h_menu_trigger.setOpen(false);
4980
4986
  }
4981
4987
  }
4982
4988
  }
@@ -5091,7 +5097,7 @@
5091
5097
  let getPlacement = function() {
5092
5098
  if (isSubmenu) {
5093
5099
  return "right-start";
5094
- } else if (menuTrigger._menu_trigger.isDropdown) {
5100
+ } else if (menuTrigger._h_menu_trigger.isDropdown) {
5095
5101
  return el.getAttribute("data-align") || "bottom-start";
5096
5102
  }
5097
5103
  return "right-start";
@@ -5136,7 +5142,7 @@
5136
5142
  el.classList.remove("hidden");
5137
5143
  el.pauseKeyEvents = false;
5138
5144
  let firstOpen = true;
5139
- if (!isSubmenu && menuTrigger._menu_trigger.isDropdown) {
5145
+ if (!isSubmenu && menuTrigger._h_menu_trigger.isDropdown) {
5140
5146
  autoUpdateCleanup = autoUpdate(parent, el, updatePosition);
5141
5147
  } else {
5142
5148
  updatePosition();
@@ -5144,8 +5150,8 @@
5144
5150
  }
5145
5151
  }
5146
5152
  function openDropdown() {
5147
- if (menuTrigger._menu_trigger.isDropdown) {
5148
- menuTrigger._menu_trigger.setOpen(true);
5153
+ if (menuTrigger._h_menu_trigger.isDropdown) {
5154
+ menuTrigger._h_menu_trigger.setOpen(true);
5149
5155
  }
5150
5156
  open(menuTrigger);
5151
5157
  }
@@ -5171,6 +5177,10 @@
5171
5177
  menuSubItem._menu_sub.open = open;
5172
5178
  menuSubItem._menu_sub.close = close;
5173
5179
  } else {
5180
+ if (menuTrigger._h_menu_trigger.navItem) {
5181
+ menuTrigger._h_menu_trigger.openMenu = openDropdown;
5182
+ menuTrigger._h_menu_trigger.closeMenu = close;
5183
+ }
5174
5184
  listenForTrigger(true);
5175
5185
  }
5176
5186
  function onTransitionEnd(event) {
@@ -5539,6 +5549,154 @@
5539
5549
  });
5540
5550
  }
5541
5551
 
5552
+ // src/components/navigation-menu.js
5553
+ var navItemTriggerClasses = [
5554
+ "inline-flex",
5555
+ "h-9",
5556
+ "w-max",
5557
+ "items-center",
5558
+ "justify-center",
5559
+ "rounded-control",
5560
+ "px-3",
5561
+ "py-2",
5562
+ "text-sm",
5563
+ "font-medium",
5564
+ "whitespace-nowrap",
5565
+ "gap-1.5",
5566
+ "transition-colors",
5567
+ "duration-100",
5568
+ "motion-reduce:transition-none",
5569
+ "hover:bg-secondary-hover",
5570
+ "hover:text-secondary-foreground",
5571
+ "focus:bg-secondary-hover",
5572
+ "focus:text-secondary-foreground",
5573
+ "outline-ring/50",
5574
+ "focus-visible:outline-[calc(var(--spacing)*0.75)]",
5575
+ "focus-visible:outline",
5576
+ "cursor-pointer",
5577
+ "shrink-0",
5578
+ "[&_svg]:shrink-0",
5579
+ "[&_svg:not([class*='size-'])]:size-4",
5580
+ "has-[>svg]:px-2.5"
5581
+ ];
5582
+ function navigation_menu_default(Alpine) {
5583
+ Alpine.directive("h-nav", (el, { original }) => {
5584
+ if (el.tagName !== "NAV") {
5585
+ throw new Error(`${original} must be a nav element`);
5586
+ }
5587
+ if (!el.hasAttribute("aria-label")) {
5588
+ throw new Error(`${original} must have an "aria-label" attribute`);
5589
+ }
5590
+ el.classList.add("relative", "z-10", "flex", "items-center");
5591
+ el.setAttribute("data-slot", "nav");
5592
+ });
5593
+ Alpine.directive("h-nav-list", (el, { original }, { Alpine: Alpine2 }) => {
5594
+ if (el.tagName !== "UL") {
5595
+ throw new Error(`${original} must be a ul element`);
5596
+ }
5597
+ const nav = Alpine2.findClosest(el.parentElement, (parent) => parent.getAttribute("data-slot") === "nav");
5598
+ if (!nav) {
5599
+ throw new Error(`${original} must be inside a ${Alpine2.prefixed("h-nav")} element`);
5600
+ }
5601
+ el.classList.add("flex", "flex-1", "list-none", "items-center", "gap-1");
5602
+ el.setAttribute("data-slot", "nav-list");
5603
+ });
5604
+ Alpine.directive("h-nav-item", (el, { original }) => {
5605
+ if (el.tagName !== "LI") {
5606
+ throw new Error(`${original} must be a li element`);
5607
+ }
5608
+ if (el.parentElement?.getAttribute("data-slot") !== "nav-list") {
5609
+ throw new Error(`${original} must be a direct child of a x-h-nav-list element`);
5610
+ }
5611
+ el.classList.add("relative");
5612
+ el.setAttribute("data-slot", "nav-item");
5613
+ });
5614
+ Alpine.directive("h-nav-trigger", (el, { original }, { cleanup, Alpine: Alpine2 }) => {
5615
+ if (el.tagName !== "BUTTON") {
5616
+ throw new Error(`${original} must be a button element`);
5617
+ }
5618
+ const navItem = Alpine2.findClosest(el.parentElement, (parent) => parent.getAttribute("data-slot") === "nav-item");
5619
+ if (!navItem) {
5620
+ throw new Error(`${original} must be inside a ${Alpine2.prefixed("h-nav-item")} element`);
5621
+ }
5622
+ if (!el.hasAttribute("id")) {
5623
+ el.setAttribute("id", `nnt${uuid_default()}`);
5624
+ }
5625
+ const chevron = createSvg({
5626
+ icon: ChevronDown,
5627
+ classes: "size-4 shrink-0 transition-transform duration-200 motion-reduce:transition-none [[data-state=open]_&]:rotate-180",
5628
+ attrs: { "aria-hidden": "true", role: "presentation" }
5629
+ });
5630
+ el._h_menu_trigger = {
5631
+ isDropdown: true,
5632
+ navItem: true,
5633
+ openMenu: void 0,
5634
+ closeMenu: void 0,
5635
+ setOpen(open) {
5636
+ el.setAttribute("aria-expanded", String(open));
5637
+ el.setAttribute("data-state", open ? "open" : "closed");
5638
+ }
5639
+ };
5640
+ el.setAttribute("aria-haspopup", "menu");
5641
+ el.setAttribute("aria-expanded", "false");
5642
+ el.setAttribute("data-state", "closed");
5643
+ el.classList.add("bg-transparent", "data-[state=open]:bg-secondary-hover", "disabled:opacity-50", "disabled:pointer-events-none", ...navItemTriggerClasses);
5644
+ el.appendChild(chevron);
5645
+ el.setAttribute("data-slot", "nav-trigger");
5646
+ const nav = Alpine2.findClosest(el.parentElement, (p) => p.getAttribute("data-slot") === "nav");
5647
+ let cancelHoverCleanup = null;
5648
+ if (nav?.hasAttribute("data-open-on-hover")) {
5649
+ let scheduleClose = function() {
5650
+ closeTimer = setTimeout(() => {
5651
+ el._h_menu_trigger.closeMenu?.();
5652
+ closeTimer = null;
5653
+ }, 100);
5654
+ }, cancelClose = function() {
5655
+ if (closeTimer) {
5656
+ clearTimeout(closeTimer);
5657
+ closeTimer = null;
5658
+ }
5659
+ }, onNavItemEnter = function() {
5660
+ cancelClose();
5661
+ el._h_menu_trigger.openMenu?.();
5662
+ };
5663
+ el.classList.remove("cursor-pointer");
5664
+ let closeTimer = null;
5665
+ navItem.addEventListener("mouseenter", onNavItemEnter);
5666
+ navItem.addEventListener("mouseleave", scheduleClose);
5667
+ cancelHoverCleanup = () => {
5668
+ cancelClose();
5669
+ navItem.removeEventListener("mouseenter", onNavItemEnter);
5670
+ navItem.removeEventListener("mouseleave", scheduleClose);
5671
+ };
5672
+ }
5673
+ cleanup(() => {
5674
+ if (cancelHoverCleanup) cancelHoverCleanup();
5675
+ if (chevron.parentElement === el) el.removeChild(chevron);
5676
+ });
5677
+ });
5678
+ Alpine.directive("h-nav-link", (el, { original }, { cleanup }) => {
5679
+ if (el.tagName !== "A" && el.tagName !== "BUTTON") {
5680
+ throw new Error(`${original} must be an anchor or button element`);
5681
+ } else if (el.tagName === "BUTTON") {
5682
+ el.setAttribute("type", "button");
5683
+ }
5684
+ el.classList.add(...navItemTriggerClasses, "no-underline", "text-inherit", "data-[active]:bg-secondary-hover", "data-[active]:font-semibold");
5685
+ function syncActive() {
5686
+ if (el.hasAttribute("data-active")) {
5687
+ el.setAttribute("aria-current", "page");
5688
+ } else {
5689
+ el.removeAttribute("aria-current");
5690
+ }
5691
+ }
5692
+ syncActive();
5693
+ const observer = new MutationObserver(syncActive);
5694
+ observer.observe(el, { attributes: true, attributeFilter: ["data-active"] });
5695
+ el.setAttribute("data-slot", "nav-link");
5696
+ cleanup(() => observer.disconnect());
5697
+ });
5698
+ }
5699
+
5542
5700
  // src/utils/breakpoint-listener.js
5543
5701
  function getBreakpointListener(handler, breakpoint = 768, frame = false) {
5544
5702
  let bps = Number.isFinite(breakpoint) ? `${breakpoint}px` : breakpoint;
@@ -9295,8 +9453,13 @@
9295
9453
  if (!storageKey) return;
9296
9454
  if (saveTimer) clearTimeout(saveTimer);
9297
9455
  saveTimer = setTimeout(() => {
9456
+ const usable = usableSize();
9457
+ if (usable <= 0) {
9458
+ saveTimer = null;
9459
+ return;
9460
+ }
9298
9461
  const visible = panels.filter((p) => !p.hidden);
9299
- const sizes = visible.map((p) => p.size / usableSize());
9462
+ const sizes = visible.map((p) => p.size / usable);
9300
9463
  localStorage.setItem(storageKey, JSON.stringify(sizes));
9301
9464
  saveTimer = null;
9302
9465
  }, SAVE_DELAY);
@@ -9331,26 +9494,44 @@
9331
9494
  const total = usableSize();
9332
9495
  if (!initialized) {
9333
9496
  initialized = true;
9334
- const visible2 = panels.filter((p) => !p.hidden);
9335
- const stored = loadSizes();
9336
- if (stored && stored.length === visible2.length) {
9337
- visible2.forEach((p, i) => {
9338
- p.size = stored[i] * usableSize();
9339
- p.explicit = true;
9340
- });
9341
- } else {
9342
- const explicitTotal = visible2.filter((p) => p.explicit).reduce((sum, p) => sum + p.declaredSize, 0);
9343
- const autoPanels = visible2.filter((p) => !p.explicit);
9344
- const remaining = total - explicitTotal;
9345
- const share = autoPanels.length ? remaining / autoPanels.length : 0;
9346
- visible2.forEach((p) => {
9347
- if (p.explicit) {
9348
- p.size = p.declaredSize;
9497
+ const anyRestore = visible.some((p) => p.restoreFraction != null);
9498
+ if (anyRestore) {
9499
+ const restoreFractionSum = visible.reduce((sum, p) => sum + (p.restoreFraction ?? 0), 0);
9500
+ const remainingSpace = total * (1 - restoreFractionSum);
9501
+ const nonRestorePanels = visible.filter((p) => p.restoreFraction == null);
9502
+ const nonRestoreDeclaredTotal = nonRestorePanels.reduce((s, p) => s + (p.declaredSize ?? 0), 0);
9503
+ visible.forEach((p) => {
9504
+ if (p.restoreFraction != null) {
9505
+ p.size = p.restoreFraction * total;
9506
+ p.explicit = true;
9507
+ p.restoreFraction = null;
9508
+ } else if (nonRestoreDeclaredTotal > 0) {
9509
+ p.size = (p.declaredSize ?? 0) / nonRestoreDeclaredTotal * remainingSpace;
9349
9510
  } else {
9350
- p.size = share;
9511
+ p.size = nonRestorePanels.length > 0 ? remainingSpace / nonRestorePanels.length : 0;
9351
9512
  }
9352
- p.size = Math.min(Math.max(p.size ?? share, p.min), p.max);
9353
9513
  });
9514
+ } else {
9515
+ const stored = loadSizes();
9516
+ if (stored && stored.length === visible.length) {
9517
+ visible.forEach((p, i) => {
9518
+ p.size = stored[i] * usableSize();
9519
+ p.explicit = true;
9520
+ });
9521
+ } else {
9522
+ const explicitTotal = visible.filter((p) => p.explicit).reduce((sum, p) => sum + p.declaredSize, 0);
9523
+ const autoPanels = visible.filter((p) => !p.explicit);
9524
+ const remaining = total - explicitTotal;
9525
+ const share = autoPanels.length ? remaining / autoPanels.length : 0;
9526
+ visible.forEach((p) => {
9527
+ if (p.explicit) {
9528
+ p.size = p.declaredSize;
9529
+ } else {
9530
+ p.size = share;
9531
+ }
9532
+ p.size = Math.min(Math.max(p.size ?? share, p.min), p.max);
9533
+ });
9534
+ }
9354
9535
  }
9355
9536
  }
9356
9537
  visible.forEach((p) => {
@@ -9363,6 +9544,10 @@
9363
9544
  let delta = total - currentTotal;
9364
9545
  if (Math.abs(delta) < DELTA_ABS) {
9365
9546
  visible.forEach((p) => p.apply());
9547
+ if (total > 0)
9548
+ visible.forEach((p) => {
9549
+ p.savedFraction = p.size / total;
9550
+ });
9366
9551
  return;
9367
9552
  }
9368
9553
  let flexible = visible.filter((p) => {
@@ -9396,6 +9581,10 @@
9396
9581
  if (Math.abs(consumed) < DELTA_ABS) break;
9397
9582
  }
9398
9583
  visible.forEach((p) => p.apply());
9584
+ if (total > 0)
9585
+ visible.forEach((p) => {
9586
+ p.savedFraction = p.size / total;
9587
+ });
9399
9588
  };
9400
9589
  let layoutFrame = null;
9401
9590
  const queueLayout = () => {
@@ -9430,7 +9619,6 @@
9430
9619
  queueLayout();
9431
9620
  },
9432
9621
  panelHidden() {
9433
- initialized = false;
9434
9622
  refreshGutters();
9435
9623
  queueLayout();
9436
9624
  },
@@ -9441,6 +9629,9 @@
9441
9629
  panelChange() {
9442
9630
  queueLayout();
9443
9631
  },
9632
+ resetInit() {
9633
+ initialized = false;
9634
+ },
9444
9635
  normalize,
9445
9636
  saveSizes
9446
9637
  };
@@ -9604,6 +9795,9 @@
9604
9795
  max: split._h_split.normalize(el.getAttribute("data-max")) ?? Infinity,
9605
9796
  collapsed: false,
9606
9797
  prevSize: null,
9798
+ prevHiddenFraction: null,
9799
+ savedFraction: null,
9800
+ restoreFraction: null,
9607
9801
  apply() {
9608
9802
  el.style.flexBasis = `${this.size.toFixed(2)}px`;
9609
9803
  if (split._h_split.state.isBorder) {
@@ -9708,7 +9902,7 @@
9708
9902
  gutter.addEventListener("pointerdown", drag);
9709
9903
  const collapse = () => {
9710
9904
  if (panel.collapsed) return;
9711
- panel.prevSize = panel.size;
9905
+ panel.prevSize = panel.size > (panel.min ?? 0) ? panel.size : panel.declaredSize;
9712
9906
  panel.size = panel.min ?? 0;
9713
9907
  panel.collapsed = true;
9714
9908
  panel.explicit = true;
@@ -9732,19 +9926,32 @@
9732
9926
  panel.explicit = true;
9733
9927
  split._h_split.panelChange();
9734
9928
  };
9929
+ const setState = () => {
9930
+ if (panel.hidden) {
9931
+ el.classList.add("hidden");
9932
+ } else {
9933
+ el.classList.remove("hidden");
9934
+ }
9935
+ split._h_split.panelHidden();
9936
+ };
9937
+ setState();
9735
9938
  const observer = new MutationObserver((mutations) => {
9736
9939
  mutations.forEach((mutation) => {
9737
9940
  if (mutation.attributeName === "data-gutterless") {
9738
9941
  gutterless = el.getAttribute("data-gutterless") === "true";
9739
9942
  split._h_split.gutterHidden();
9740
9943
  } else if (mutation.attributeName === "data-hidden") {
9741
- panel.hidden = el.getAttribute("data-hidden") === "true";
9742
- if (panel.hidden) {
9743
- el.classList.add("hidden");
9744
- } else {
9745
- el.classList.remove("hidden");
9944
+ const newHidden = el.getAttribute("data-hidden") === "true";
9945
+ if (!panel.hidden && newHidden) {
9946
+ panel.prevHiddenFraction = panel.savedFraction;
9947
+ } else if (panel.hidden && !newHidden) {
9948
+ if (panel.prevHiddenFraction != null) {
9949
+ panel.restoreFraction = panel.prevHiddenFraction;
9950
+ }
9951
+ split._h_split.resetInit();
9746
9952
  }
9747
- split._h_split.panelHidden();
9953
+ panel.hidden = newHidden;
9954
+ setState();
9748
9955
  } else if (mutation.attributeName === "data-locked") {
9749
9956
  panel.setLocked();
9750
9957
  } else {
@@ -11717,7 +11924,7 @@
11717
11924
  }
11718
11925
 
11719
11926
  // package.json
11720
- var version = "1.11.0";
11927
+ var version = "1.12.0";
11721
11928
 
11722
11929
  // src/index.js
11723
11930
  window.Harmonia = { getBreakpointListener, addColorSchemeListener, getColorScheme, removeColorSchemeListener, setColorScheme, getSystemColorScheme, version };
@@ -11742,6 +11949,7 @@
11742
11949
  window.Alpine.plugin(label_default);
11743
11950
  window.Alpine.plugin(list_default);
11744
11951
  window.Alpine.plugin(menu_default);
11952
+ window.Alpine.plugin(navigation_menu_default);
11745
11953
  window.Alpine.plugin(notifications_default);
11746
11954
  window.Alpine.plugin(pagination_default);
11747
11955
  window.Alpine.plugin(popover_default);