@onyxdesign/core 1.0.0 → 1.1.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/onyx.css +244 -2
  3. package/src/onyx.js +127 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onyxdesign/core",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A black & white, zero-dependency UI component library for people who care about the details.",
5
5
  "main": "src/onyx.js",
6
6
  "style": "src/onyx.css",
package/src/onyx.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Onyx v1.0.0 — a black & white component library for the web
2
+ * Onyx v1.1.0 — a black & white component library for the web
3
3
  * Core: MIT License. Onyx Pro components require a commercial license.
4
4
  */
5
5
 
@@ -847,8 +847,84 @@
847
847
  transition: color var(--ox-duration) var(--ox-ease), background var(--ox-duration) var(--ox-ease);
848
848
  }
849
849
  .ox-navbar-links a:hover { color: var(--ox-fg); background: var(--ox-bg-muted); }
850
+
851
+ /* Mobile menu: links collapse behind a toggle below 760px
852
+ (or below 1024px with data-collapse="lg" on .ox-navbar) */
853
+ .ox-navbar-toggle { display: none; }
854
+ .ox-navbar-menu { display: none; }
855
+ .ox-navbar-toggle {
856
+ position: relative;
857
+ width: 2.5rem;
858
+ height: 2.5rem;
859
+ padding: 0;
860
+ color: var(--ox-fg);
861
+ background: transparent;
862
+ border: 1px solid var(--ox-border);
863
+ border-radius: var(--ox-radius-full);
864
+ cursor: pointer;
865
+ }
866
+ .ox-navbar-toggle span {
867
+ position: absolute;
868
+ left: 50%;
869
+ top: 50%;
870
+ width: 1rem;
871
+ height: 1.5px;
872
+ margin: -0.75px 0 0 -0.5rem;
873
+ background: currentColor;
874
+ border-radius: 2px;
875
+ transition: transform 260ms var(--ox-ease);
876
+ }
877
+ .ox-navbar-toggle span:first-child { transform: translateY(-3.5px); }
878
+ .ox-navbar-toggle span:last-child { transform: translateY(3.5px); }
879
+ .ox-navbar[data-open="true"] .ox-navbar-toggle span:first-child { transform: rotate(45deg); }
880
+ .ox-navbar[data-open="true"] .ox-navbar-toggle span:last-child { transform: rotate(-45deg); }
881
+ /* Absolute, not fixed: the navbar's backdrop-filter makes it the containing block
882
+ for fixed children, so the menu is sized from the navbar's bottom edge instead */
883
+ .ox-navbar-menu {
884
+ position: absolute;
885
+ top: 100%;
886
+ left: 0;
887
+ right: 0;
888
+ height: calc(100vh - 3.75rem);
889
+ height: calc(100dvh - 3.75rem);
890
+ z-index: 99;
891
+ flex-direction: column;
892
+ gap: var(--ox-space-1);
893
+ padding: var(--ox-space-4) var(--ox-space-4) var(--ox-space-8);
894
+ overflow-y: auto;
895
+ background: var(--ox-bg);
896
+ border-top: 1px solid var(--ox-border);
897
+ opacity: 0;
898
+ transform: translateY(-8px);
899
+ visibility: hidden;
900
+ transition: opacity 220ms var(--ox-ease), transform 260ms var(--ox-ease), visibility 260ms;
901
+ }
902
+ .ox-navbar[data-open="true"] .ox-navbar-menu { opacity: 1; transform: none; visibility: visible; }
903
+ .ox-navbar-menu > a:not(.ox-btn) {
904
+ display: flex;
905
+ align-items: center;
906
+ justify-content: space-between;
907
+ min-height: 3.25rem;
908
+ padding: 0 var(--ox-space-2);
909
+ font-size: var(--ox-text-lg);
910
+ font-weight: 500;
911
+ letter-spacing: -0.01em;
912
+ color: var(--ox-fg);
913
+ text-decoration: none;
914
+ border-bottom: 1px solid var(--ox-border);
915
+ }
916
+ .ox-navbar-menu > a:not(.ox-btn)::after { content: "→"; color: var(--ox-fg-subtle); }
917
+ .ox-navbar-menu .ox-btn { margin-top: var(--ox-space-4); }
918
+ body.ox-nav-locked { overflow: hidden; }
850
919
  @media (max-width: 760px) {
851
920
  .ox-navbar-links { display: none; }
921
+ .ox-navbar-toggle { display: inline-grid; }
922
+ .ox-navbar-menu { display: flex; }
923
+ }
924
+ @media (max-width: 1023px) {
925
+ .ox-navbar[data-collapse="lg"] .ox-navbar-links { display: none; }
926
+ .ox-navbar[data-collapse="lg"] .ox-navbar-toggle { display: inline-grid; }
927
+ .ox-navbar[data-collapse="lg"] .ox-navbar-menu { display: flex; }
852
928
  }
853
929
 
854
930
  /* =========================================================
@@ -867,7 +943,169 @@
867
943
  }
868
944
 
869
945
  /* =========================================================
870
- 19. Keyframes & motion preferences
946
+ 19. Breadcrumb & pagination
947
+ ========================================================= */
948
+ .ox-breadcrumb ol { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ox-space-2); margin: 0; padding: 0; list-style: none; font-size: var(--ox-text-sm); }
949
+ .ox-breadcrumb li { display: inline-flex; align-items: center; gap: var(--ox-space-2); color: var(--ox-fg-muted); }
950
+ .ox-breadcrumb li + li::before { content: "/"; color: var(--ox-fg-subtle); }
951
+ .ox-breadcrumb a { color: inherit; text-decoration: none; border-radius: var(--ox-radius-sm); transition: color var(--ox-duration); }
952
+ .ox-breadcrumb a:hover { color: var(--ox-fg); }
953
+ .ox-breadcrumb [aria-current="page"] { color: var(--ox-fg); font-weight: 500; }
954
+
955
+ .ox-pagination ul { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ox-space-1); margin: 0; padding: 0; list-style: none; }
956
+ .ox-pagination a,
957
+ .ox-pagination span {
958
+ display: inline-grid;
959
+ place-items: center;
960
+ min-width: 2.25rem;
961
+ height: 2.25rem;
962
+ padding: 0 var(--ox-space-2);
963
+ font-size: var(--ox-text-sm);
964
+ font-variant-numeric: tabular-nums;
965
+ color: var(--ox-fg-muted);
966
+ text-decoration: none;
967
+ border: 1px solid transparent;
968
+ border-radius: var(--ox-radius);
969
+ transition: background var(--ox-duration), color var(--ox-duration), border-color var(--ox-duration);
970
+ }
971
+ .ox-pagination a:hover { color: var(--ox-fg); background: var(--ox-bg-muted); }
972
+ .ox-pagination [aria-current="page"] { color: var(--ox-inverse); background: var(--ox-fg); border-color: var(--ox-fg); }
973
+ .ox-pagination [aria-disabled="true"] { opacity: 0.35; pointer-events: none; }
974
+ .ox-pagination .ox-gap { min-width: 1.5rem; }
975
+
976
+ /* =========================================================
977
+ 20. Range slider & number stepper
978
+ ========================================================= */
979
+ .ox-range {
980
+ --_pct: 50%;
981
+ -webkit-appearance: none;
982
+ appearance: none;
983
+ width: 100%;
984
+ height: 1.5rem;
985
+ margin: 0;
986
+ background: transparent;
987
+ cursor: pointer;
988
+ }
989
+ .ox-range::-webkit-slider-runnable-track { height: 4px; border-radius: 99px; background: linear-gradient(to right, var(--ox-fg) var(--_pct), var(--ox-bg-muted) var(--_pct)); }
990
+ .ox-range::-moz-range-track { height: 4px; border-radius: 99px; background: var(--ox-bg-muted); }
991
+ .ox-range::-moz-range-progress { height: 4px; border-radius: 99px; background: var(--ox-fg); }
992
+ .ox-range::-webkit-slider-thumb { -webkit-appearance: none; width: 1.25rem; height: 1.25rem; margin-top: calc(2px - 0.625rem); border-radius: 50%; background: var(--ox-bg); border: 2px solid var(--ox-fg); box-shadow: var(--ox-shadow-sm); transition: transform var(--ox-duration) var(--ox-ease); }
993
+ .ox-range::-moz-range-thumb { width: 1rem; height: 1rem; border-radius: 50%; background: var(--ox-bg); border: 2px solid var(--ox-fg); }
994
+ .ox-range:active::-webkit-slider-thumb { transform: scale(1.15); }
995
+ .ox-range:focus-visible { outline: none; }
996
+ .ox-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--ox-ring); }
997
+ .ox-range:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px var(--ox-ring); }
998
+
999
+ .ox-stepper { display: inline-flex; align-items: stretch; height: 2.5rem; background: var(--ox-surface); border: 1px solid var(--ox-border); border-radius: var(--ox-radius); overflow: hidden; }
1000
+ .ox-stepper:focus-within { border-color: var(--ox-fg); box-shadow: 0 0 0 3px var(--ox-ring); }
1001
+ .ox-stepper button { width: 2.5rem; padding: 0; font-size: var(--ox-text-lg); line-height: 1; color: var(--ox-fg); background: transparent; border: 0; cursor: pointer; transition: background var(--ox-duration); }
1002
+ .ox-stepper button:hover { background: var(--ox-bg-muted); }
1003
+ .ox-stepper button:disabled { opacity: 0.3; cursor: not-allowed; background: transparent; }
1004
+ .ox-stepper input { width: 3.5rem; padding: 0; font: inherit; font-size: var(--ox-text-sm); font-variant-numeric: tabular-nums; text-align: center; color: var(--ox-fg); background: transparent; border: 0; border-inline: 1px solid var(--ox-border); outline: none; -moz-appearance: textfield; }
1005
+ .ox-stepper input::-webkit-outer-spin-button,
1006
+ .ox-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
1007
+
1008
+ /* =========================================================
1009
+ 21. Chips (toggleable filters)
1010
+ ========================================================= */
1011
+ .ox-chips { display: flex; flex-wrap: wrap; gap: var(--ox-space-2); }
1012
+ .ox-chip {
1013
+ display: inline-flex;
1014
+ align-items: center;
1015
+ gap: 0.4rem;
1016
+ height: 2rem;
1017
+ padding: 0 var(--ox-space-3);
1018
+ font: 500 var(--ox-text-sm) var(--ox-font-sans);
1019
+ color: var(--ox-fg-muted);
1020
+ background: var(--ox-surface);
1021
+ border: 1px solid var(--ox-border);
1022
+ border-radius: var(--ox-radius-full);
1023
+ cursor: pointer;
1024
+ transition: color var(--ox-duration), background var(--ox-duration), border-color var(--ox-duration), transform var(--ox-duration) var(--ox-ease);
1025
+ }
1026
+ .ox-chip:hover { color: var(--ox-fg); border-color: var(--ox-border-strong); }
1027
+ .ox-chip:active { transform: scale(0.97); }
1028
+ .ox-chip[aria-pressed="true"] { color: var(--ox-inverse); background: var(--ox-fg); border-color: var(--ox-fg); }
1029
+ .ox-chip[aria-pressed="true"]::before { content: "✓"; font-size: 0.7rem; }
1030
+
1031
+ /* =========================================================
1032
+ 22. Table
1033
+ ========================================================= */
1034
+ .ox-table-wrap { overflow-x: auto; border: 1px solid var(--ox-border); border-radius: var(--ox-radius-lg); background: var(--ox-surface); }
1035
+ .ox-table { width: 100%; border-collapse: collapse; font-size: var(--ox-text-sm); font-variant-numeric: tabular-nums; }
1036
+ .ox-table caption { padding: var(--ox-space-3) var(--ox-space-4); text-align: left; color: var(--ox-fg-muted); caption-side: bottom; font-size: var(--ox-text-xs); }
1037
+ .ox-table th { padding: var(--ox-space-3) var(--ox-space-4); text-align: left; font-size: var(--ox-text-xs); font-weight: 500; color: var(--ox-fg-muted); background: var(--ox-bg-subtle); border-bottom: 1px solid var(--ox-border); white-space: nowrap; }
1038
+ .ox-table td { padding: var(--ox-space-3) var(--ox-space-4); border-bottom: 1px solid var(--ox-border); }
1039
+ .ox-table tbody tr:last-child td { border-bottom: 0; }
1040
+ .ox-table tbody tr { transition: background var(--ox-duration); }
1041
+ .ox-table tbody tr:hover { background: var(--ox-bg-subtle); }
1042
+ .ox-table .num { text-align: right; }
1043
+ .ox-table-striped tbody tr:nth-child(even) { background: var(--ox-bg-subtle); }
1044
+
1045
+ /* =========================================================
1046
+ 23. Empty state & timeline
1047
+ ========================================================= */
1048
+ .ox-empty { display: grid; justify-items: center; gap: var(--ox-space-2); padding: var(--ox-space-12) var(--ox-space-6); text-align: center; border: 1.5px dashed var(--ox-border-strong); border-radius: var(--ox-radius-lg); }
1049
+ .ox-empty-icon { display: grid; place-items: center; width: 3rem; height: 3rem; margin-bottom: var(--ox-space-2); font-size: var(--ox-text-xl); color: var(--ox-fg); background: var(--ox-bg-muted); border-radius: 50%; }
1050
+ .ox-empty-title { margin: 0; font-size: var(--ox-text-base); font-weight: 600; }
1051
+ .ox-empty-text { max-width: 36ch; margin: 0 0 var(--ox-space-3); font-size: var(--ox-text-sm); color: var(--ox-fg-muted); }
1052
+
1053
+ .ox-timeline { margin: 0; padding: 0; list-style: none; }
1054
+ .ox-timeline li { position: relative; padding: 0 0 var(--ox-space-6) var(--ox-space-8); }
1055
+ .ox-timeline li::before { content: ""; position: absolute; left: 0.3rem; top: 0.45rem; width: 0.65rem; height: 0.65rem; border-radius: 50%; background: var(--ox-bg); border: 2px solid var(--ox-fg); }
1056
+ .ox-timeline li::after { content: ""; position: absolute; left: calc(0.3rem + 0.325rem - 0.5px); top: 1.4rem; bottom: 0.2rem; width: 1px; background: var(--ox-border-strong); }
1057
+ .ox-timeline li:last-child { padding-bottom: 0; }
1058
+ .ox-timeline li:last-child::after { display: none; }
1059
+ .ox-timeline li[data-state="done"]::before { background: var(--ox-fg); }
1060
+ .ox-timeline li[data-state="upcoming"]::before { border-color: var(--ox-border-strong); border-style: dashed; }
1061
+ .ox-timeline time { display: block; margin-bottom: 2px; font: 400 var(--ox-text-xs) var(--ox-font-mono); color: var(--ox-fg-subtle); }
1062
+ .ox-timeline b { display: block; font-size: var(--ox-text-sm); font-weight: 500; }
1063
+ .ox-timeline p { margin: 2px 0 0; font-size: var(--ox-text-sm); color: var(--ox-fg-muted); }
1064
+
1065
+ /* =========================================================
1066
+ 24. Banner (dismissible announcement)
1067
+ ========================================================= */
1068
+ .ox-banner {
1069
+ position: relative;
1070
+ display: flex;
1071
+ flex-wrap: wrap;
1072
+ align-items: center;
1073
+ justify-content: center;
1074
+ gap: var(--ox-space-2) var(--ox-space-4);
1075
+ padding: var(--ox-space-2) 3rem;
1076
+ font-size: var(--ox-text-sm);
1077
+ text-align: center;
1078
+ color: var(--ox-inverse);
1079
+ background: var(--ox-fg);
1080
+ }
1081
+ .ox-banner a { color: inherit; font-weight: 500; text-underline-offset: 3px; }
1082
+ .ox-banner-close { position: absolute; right: var(--ox-space-2); top: 50%; display: grid; place-items: center; width: 2rem; height: 2rem; margin-top: -1rem; padding: 0; color: inherit; background: transparent; border: 0; border-radius: var(--ox-radius-sm); opacity: 0.7; cursor: pointer; }
1083
+ .ox-banner-close:hover { opacity: 1; background: color-mix(in srgb, var(--ox-inverse) 15%, transparent); }
1084
+ .ox-banner[hidden] { display: none; }
1085
+
1086
+ /* =========================================================
1087
+ 25. Avatar status
1088
+ ========================================================= */
1089
+ .ox-avatar[data-status] { position: relative; overflow: visible; }
1090
+ .ox-avatar[data-status]::after { content: ""; position: absolute; right: -1px; bottom: -1px; width: 28%; height: 28%; min-width: 8px; min-height: 8px; border-radius: 50%; border: 2px solid var(--ox-surface); }
1091
+ .ox-avatar[data-status="online"]::after { background: var(--ox-fg); }
1092
+ .ox-avatar[data-status="away"]::after { background: var(--ox-bg); box-shadow: inset 0 0 0 2px var(--ox-fg); }
1093
+ .ox-avatar[data-status="offline"]::after { background: var(--ox-border-strong); }
1094
+
1095
+ /* =========================================================
1096
+ 26. Touch screens: comfortable tap targets
1097
+ ========================================================= */
1098
+ @media (pointer: coarse) {
1099
+ .ox-btn-sm, .ox-chip, .ox-pagination a, .ox-pagination span { min-height: 2.5rem; }
1100
+ .ox-btn-sm.ox-btn-icon { width: 2.5rem; }
1101
+ .ox-segmented button { height: 2.25rem; }
1102
+ .ox-tab { padding-block: var(--ox-space-3); }
1103
+ .ox-dropdown-item { min-height: 2.75rem; }
1104
+ .ox-checkbox, .ox-radio, .ox-switch { min-height: 2.25rem; }
1105
+ }
1106
+
1107
+ /* =========================================================
1108
+ 27. Keyframes & motion preferences
871
1109
  ========================================================= */
872
1110
  @keyframes ox-spin { to { transform: rotate(360deg); } }
873
1111
  @keyframes ox-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@@ -884,3 +1122,7 @@
884
1122
  transition-duration: 1ms !important;
885
1123
  }
886
1124
  }
1125
+
1126
+ /* Theme switch: the new theme is revealed by a clip-path circle (see Onyx.theme.set) */
1127
+ ::view-transition-old(root),
1128
+ ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
package/src/onyx.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Onyx v1.0.0 — interactive behaviors
2
+ * Onyx v1.1.0 — interactive behaviors
3
3
  * Zero dependencies. Works via data attributes, or the window.Onyx API.
4
4
  */
5
5
  (function (global) {
@@ -107,11 +107,25 @@
107
107
  return global.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
108
108
  }
109
109
 
110
- function setTheme(theme) {
110
+ function applyTheme(theme) {
111
111
  document.documentElement.setAttribute("data-theme", theme);
112
112
  try { localStorage.setItem(THEME_KEY, theme); } catch (e) { /* storage unavailable */ }
113
113
  }
114
114
 
115
+ // With a point ({ x, y }), the new theme spreads out from it like ink, where the browser supports it
116
+ function setTheme(theme, from) {
117
+ var reduce = global.matchMedia && matchMedia("(prefers-reduced-motion: reduce)").matches;
118
+ if (!from || reduce || !document.startViewTransition) { applyTheme(theme); return; }
119
+ var r = Math.hypot(Math.max(from.x, innerWidth - from.x), Math.max(from.y, innerHeight - from.y));
120
+ var vt = document.startViewTransition(function () { applyTheme(theme); });
121
+ vt.ready.then(function () {
122
+ document.documentElement.animate(
123
+ { clipPath: ["circle(0px at " + from.x + "px " + from.y + "px)", "circle(" + r + "px at " + from.x + "px " + from.y + "px)"] },
124
+ { duration: 520, easing: "cubic-bezier(0.2, 0.8, 0.2, 1)", pseudoElement: "::view-transition-new(root)" }
125
+ );
126
+ }).catch(function () {});
127
+ }
128
+
115
129
  function restoreTheme() {
116
130
  try {
117
131
  var saved = localStorage.getItem(THEME_KEY);
@@ -125,10 +139,101 @@
125
139
  document.querySelectorAll(".ox-navbar").forEach(function (n) { n.setAttribute("data-scrolled", scrolled); });
126
140
  }
127
141
 
142
+ /* ---------- Mobile navigation menu ---------- */
143
+ function setNav(nav, open) {
144
+ if (!nav) return;
145
+ nav.setAttribute("data-open", String(open));
146
+ var toggle = nav.querySelector("[data-ox-nav-toggle]");
147
+ if (toggle) {
148
+ toggle.setAttribute("aria-expanded", String(open));
149
+ toggle.setAttribute("aria-label", open ? "Close menu" : "Open menu");
150
+ }
151
+ document.body.classList.toggle("ox-nav-locked", open);
152
+ }
153
+
154
+ /* ---------- Range slider fill ---------- */
155
+ function syncRange(r) {
156
+ var min = +r.min || 0, max = r.max === "" ? 100 : +r.max;
157
+ r.style.setProperty("--_pct", ((+r.value - min) / (max - min || 1)) * 100 + "%");
158
+ }
159
+
160
+ /* ---------- Number stepper ---------- */
161
+ function step(btn) {
162
+ var box = btn.closest(".ox-stepper");
163
+ var input = box.querySelector("input");
164
+ var by = +btn.getAttribute("data-step") * (+input.step || 1);
165
+ var min = input.min === "" ? -Infinity : +input.min;
166
+ var max = input.max === "" ? Infinity : +input.max;
167
+ var next = Math.min(max, Math.max(min, (+input.value || 0) + by));
168
+ input.value = +next.toFixed(6);
169
+ syncStepper(box);
170
+ input.dispatchEvent(new Event("input", { bubbles: true }));
171
+ input.dispatchEvent(new Event("change", { bubbles: true }));
172
+ }
173
+ function syncStepper(box) {
174
+ var input = box.querySelector("input");
175
+ var v = +input.value;
176
+ box.querySelectorAll("[data-step]").forEach(function (b) {
177
+ var dir = +b.getAttribute("data-step");
178
+ b.disabled = dir < 0 ? input.min !== "" && v <= +input.min : input.max !== "" && v >= +input.max;
179
+ });
180
+ }
181
+
182
+ /* ---------- Chips ---------- */
183
+ function toggleChip(chip) {
184
+ var group = chip.closest(".ox-chips");
185
+ var on = chip.getAttribute("aria-pressed") !== "true";
186
+ if (group && group.hasAttribute("data-single")) {
187
+ group.querySelectorAll(".ox-chip").forEach(function (c) { c.setAttribute("aria-pressed", "false"); });
188
+ on = true;
189
+ }
190
+ chip.setAttribute("aria-pressed", String(on));
191
+ if (group) {
192
+ var values = Array.prototype.filter.call(group.querySelectorAll(".ox-chip"), function (c) { return c.getAttribute("aria-pressed") === "true"; })
193
+ .map(function (c) { return c.value || c.textContent.trim(); });
194
+ group.dispatchEvent(new CustomEvent("ox:change", { bubbles: true, detail: { value: values } }));
195
+ }
196
+ }
197
+
198
+ function initWidgets(root) {
199
+ (root || document).querySelectorAll(".ox-range").forEach(syncRange);
200
+ (root || document).querySelectorAll(".ox-stepper").forEach(syncStepper);
201
+ // Banners dismissed before stay hidden
202
+ (root || document).querySelectorAll(".ox-banner[data-ox-banner]").forEach(function (b) {
203
+ try { if (localStorage.getItem("ox-banner-" + b.getAttribute("data-ox-banner"))) b.hidden = true; } catch (e) { /* storage unavailable */ }
204
+ });
205
+ }
206
+
128
207
  /* ---------- Event delegation ---------- */
208
+ document.addEventListener("input", function (e) {
209
+ if (e.target.classList && e.target.classList.contains("ox-range")) syncRange(e.target);
210
+ var box = e.target.closest && e.target.closest(".ox-stepper");
211
+ if (box) syncStepper(box);
212
+ });
213
+
129
214
  document.addEventListener("click", function (e) {
130
215
  var t = e.target;
131
216
 
217
+ var navToggle = t.closest("[data-ox-nav-toggle]");
218
+ if (navToggle) { var nav = navToggle.closest(".ox-navbar"); setNav(nav, nav.getAttribute("data-open") !== "true"); return; }
219
+ if (t.closest(".ox-navbar-menu a")) setNav(t.closest(".ox-navbar"), false);
220
+
221
+ var stepBtn = t.closest(".ox-stepper [data-step]");
222
+ if (stepBtn) { step(stepBtn); return; }
223
+
224
+ var chip = t.closest(".ox-chip");
225
+ if (chip) { toggleChip(chip); return; }
226
+
227
+ var dismiss = t.closest("[data-ox-dismiss]");
228
+ if (dismiss) {
229
+ var banner = dismiss.closest(".ox-banner");
230
+ if (banner) {
231
+ banner.hidden = true;
232
+ try { if (banner.getAttribute("data-ox-banner")) localStorage.setItem("ox-banner-" + banner.getAttribute("data-ox-banner"), "1"); } catch (err) { /* storage unavailable */ }
233
+ }
234
+ return;
235
+ }
236
+
132
237
  var opener = t.closest("[data-ox-modal-open]");
133
238
  if (opener) { openModal(opener.getAttribute("data-ox-modal-open")); return; }
134
239
 
@@ -160,7 +265,11 @@
160
265
  var toaster = t.closest("[data-ox-toast]");
161
266
  if (toaster) toast(toaster.getAttribute("data-ox-toast"));
162
267
 
163
- if (t.closest("[data-ox-theme-toggle]")) setTheme(currentTheme() === "dark" ? "light" : "dark");
268
+ var themeBtn = t.closest("[data-ox-theme-toggle]");
269
+ if (themeBtn) {
270
+ var r = themeBtn.getBoundingClientRect();
271
+ setTheme(currentTheme() === "dark" ? "light" : "dark", { x: r.left + r.width / 2, y: r.top + r.height / 2 });
272
+ }
164
273
  });
165
274
 
166
275
  document.addEventListener("keydown", function (e) {
@@ -168,6 +277,8 @@
168
277
  if (e.key === "Escape") {
169
278
  if (openModalEl) closeModal(openModalEl);
170
279
  closeAllDropdowns();
280
+ var openNav = document.querySelector('.ox-navbar[data-open="true"]');
281
+ if (openNav) { setNav(openNav, false); var tg = openNav.querySelector("[data-ox-nav-toggle]"); if (tg) tg.focus(); }
171
282
  }
172
283
  if (e.key === "Tab" && openModalEl) trapFocus(openModalEl, e);
173
284
 
@@ -183,14 +294,24 @@
183
294
  });
184
295
 
185
296
  global.addEventListener("scroll", syncNavbars, { passive: true });
186
- document.addEventListener("DOMContentLoaded", syncNavbars);
297
+ // Close the mobile menu if the window grows past the breakpoint while it's open
298
+ global.addEventListener("resize", function () {
299
+ document.querySelectorAll('.ox-navbar[data-open="true"]').forEach(function (n) {
300
+ var toggle = n.querySelector("[data-ox-nav-toggle]");
301
+ if (toggle && getComputedStyle(toggle).display === "none") setNav(n, false);
302
+ });
303
+ });
304
+ document.addEventListener("DOMContentLoaded", function () { syncNavbars(); initWidgets(); });
305
+ if (document.readyState !== "loading") initWidgets();
187
306
  restoreTheme();
188
307
 
189
308
  global.Onyx = {
190
- version: "1.0.0",
309
+ version: "1.1.0",
191
310
  modal: { open: openModal, close: closeModal },
192
311
  toast: toast,
193
312
  copy: copy,
194
- theme: { get: currentTheme, set: setTheme }
313
+ theme: { get: currentTheme, set: setTheme },
314
+ nav: { open: function (n) { setNav(n || document.querySelector(".ox-navbar"), true); }, close: function (n) { setNav(n || document.querySelector(".ox-navbar"), false); } },
315
+ init: initWidgets
195
316
  };
196
317
  })(window);