@collectivetrait/lineaui 0.0.0-stage → 1.0.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 (130) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/config.json +3 -0
  5. package/dist/blocks.css +5042 -0
  6. package/dist/blocks.css.map +1 -0
  7. package/dist/bundle.js +873 -0
  8. package/dist/core.css +1641 -0
  9. package/dist/core.css.map +1 -0
  10. package/dist/icons-manifest.json +1 -0
  11. package/dist/icons.css +158 -0
  12. package/dist/styles.css +10546 -0
  13. package/dist/styles.css.map +1 -0
  14. package/dist/theme-init.js +8 -0
  15. package/dist/utilities.css +3865 -0
  16. package/dist/utilities.css.map +1 -0
  17. package/humans.txt +8 -0
  18. package/js/config.js +3 -0
  19. package/js/index.js +45 -0
  20. package/js/modules/copy.js +64 -0
  21. package/js/modules/dialog.js +55 -0
  22. package/js/modules/dismiss.js +43 -0
  23. package/js/modules/dropdown.js +108 -0
  24. package/js/modules/gallery.js +84 -0
  25. package/js/modules/highlight.js +89 -0
  26. package/js/modules/navigation.js +104 -0
  27. package/js/modules/quantity.js +63 -0
  28. package/js/modules/rhythm.js +21 -0
  29. package/js/modules/table.js +109 -0
  30. package/js/modules/tabs.js +112 -0
  31. package/js/modules/toast.js +97 -0
  32. package/js/modules/tooltip.js +106 -0
  33. package/js/theme-init.js +8 -0
  34. package/package.json +71 -4
  35. package/scss/_blocks-only.scss +4 -0
  36. package/scss/_core.scss +7 -0
  37. package/scss/_index.scss +12 -0
  38. package/scss/_layers.scss +2 -0
  39. package/scss/_utilities-only.scss +4 -0
  40. package/scss/base/_code.scss +50 -0
  41. package/scss/base/_forms.scss +110 -0
  42. package/scss/base/_index.scss +7 -0
  43. package/scss/base/_interactive.scss +25 -0
  44. package/scss/base/_media.scss +28 -0
  45. package/scss/base/_reset.scss +53 -0
  46. package/scss/base/_tables.scss +49 -0
  47. package/scss/base/_typography.scss +232 -0
  48. package/scss/blocks/_accordion.scss +88 -0
  49. package/scss/blocks/_alert.scss +101 -0
  50. package/scss/blocks/_avatar.scss +104 -0
  51. package/scss/blocks/_badge.scss +72 -0
  52. package/scss/blocks/_breadcrumb.scss +65 -0
  53. package/scss/blocks/_button-group.scss +73 -0
  54. package/scss/blocks/_button.scss +212 -0
  55. package/scss/blocks/_card.scss +146 -0
  56. package/scss/blocks/_choice.scss +163 -0
  57. package/scss/blocks/_code.scss +157 -0
  58. package/scss/blocks/_data-list.scss +92 -0
  59. package/scss/blocks/_divider.scss +54 -0
  60. package/scss/blocks/_drawer.scss +160 -0
  61. package/scss/blocks/_dropdown.scss +167 -0
  62. package/scss/blocks/_empty-state.scss +61 -0
  63. package/scss/blocks/_field.scss +146 -0
  64. package/scss/blocks/_form.scss +87 -0
  65. package/scss/blocks/_gallery.scss +120 -0
  66. package/scss/blocks/_index.scss +49 -0
  67. package/scss/blocks/_input-group.scss +95 -0
  68. package/scss/blocks/_line-item.scss +105 -0
  69. package/scss/blocks/_list.scss +117 -0
  70. package/scss/blocks/_media.scss +105 -0
  71. package/scss/blocks/_menu.scss +191 -0
  72. package/scss/blocks/_meta.scss +59 -0
  73. package/scss/blocks/_modal.scss +156 -0
  74. package/scss/blocks/_navbar.scss +227 -0
  75. package/scss/blocks/_order-summary.scss +92 -0
  76. package/scss/blocks/_page-header.scss +88 -0
  77. package/scss/blocks/_pagination.scss +110 -0
  78. package/scss/blocks/_price.scss +88 -0
  79. package/scss/blocks/_product-card.scss +138 -0
  80. package/scss/blocks/_progress.scss +68 -0
  81. package/scss/blocks/_prose.scss +165 -0
  82. package/scss/blocks/_quantity.scss +73 -0
  83. package/scss/blocks/_quote.scss +71 -0
  84. package/scss/blocks/_rating.scss +50 -0
  85. package/scss/blocks/_site-footer.scss +135 -0
  86. package/scss/blocks/_skeleton.scss +63 -0
  87. package/scss/blocks/_spinner.scss +44 -0
  88. package/scss/blocks/_stat.scss +111 -0
  89. package/scss/blocks/_steps.scss +147 -0
  90. package/scss/blocks/_swatch.scss +117 -0
  91. package/scss/blocks/_table.scss +242 -0
  92. package/scss/blocks/_tabs.scss +168 -0
  93. package/scss/blocks/_tag.scss +72 -0
  94. package/scss/blocks/_timeline.scss +84 -0
  95. package/scss/blocks/_toast.scss +146 -0
  96. package/scss/blocks/_toolbar.scss +67 -0
  97. package/scss/blocks/_tooltip.scss +63 -0
  98. package/scss/helpers/_index.scss +2 -0
  99. package/scss/helpers/_rhythm.scss +64 -0
  100. package/scss/helpers/_typography.scss +4 -0
  101. package/scss/layout/_cluster.scss +43 -0
  102. package/scss/layout/_container.scss +26 -0
  103. package/scss/layout/_grid.scss +90 -0
  104. package/scss/layout/_index.scss +5 -0
  105. package/scss/layout/_section.scss +78 -0
  106. package/scss/layout/_shell.scss +87 -0
  107. package/scss/settings/_blocks.scss +3 -0
  108. package/scss/settings/_breakpoints.scss +7 -0
  109. package/scss/settings/_fonts.scss +5 -0
  110. package/scss/settings/_index.scss +7 -0
  111. package/scss/settings/_layout.scss +5 -0
  112. package/scss/settings/_prefix.scss +1 -0
  113. package/scss/settings/_rhythm.scss +6 -0
  114. package/scss/settings/_utilities.scss +2 -0
  115. package/scss/tokens/_borders.scss +16 -0
  116. package/scss/tokens/_elevation.scss +37 -0
  117. package/scss/tokens/_index.scss +8 -0
  118. package/scss/tokens/_layout.scss +17 -0
  119. package/scss/tokens/_motion.scss +17 -0
  120. package/scss/tokens/_palette.scss +50 -0
  121. package/scss/tokens/_roles.scss +90 -0
  122. package/scss/tokens/_space.scss +9 -0
  123. package/scss/tokens/_type.scss +57 -0
  124. package/scss/tools/_bem.scss +22 -0
  125. package/scss/tools/_index.scss +4 -0
  126. package/scss/tools/_rhythm.scss +26 -0
  127. package/scss/tools/_role-engine.scss +16 -0
  128. package/scss/tools/_utility-generator.scss +74 -0
  129. package/scss/utilities/_index.scss +8 -0
  130. package/scss/utilities/_map.scss +321 -0
package/dist/bundle.js ADDED
@@ -0,0 +1,873 @@
1
+ // config.json
2
+ var config_default = {
3
+ prefix: "ln-"
4
+ };
5
+
6
+ // js/config.js
7
+ var prefix = config_default.prefix;
8
+
9
+ // js/modules/rhythm.js
10
+ var ATTR = `data-${prefix}grid-toggle`;
11
+ function initRhythm() {
12
+ document.querySelectorAll(`[${ATTR}]`).forEach((trigger) => {
13
+ if (trigger._lnGridToggle) return;
14
+ trigger._lnGridToggle = true;
15
+ trigger.addEventListener("click", () => {
16
+ const selector = trigger.getAttribute(ATTR);
17
+ const target = selector ? document.querySelector(selector) : trigger.parentElement;
18
+ if (!target) return;
19
+ const active = target.classList.toggle("show-grid");
20
+ trigger.setAttribute("aria-pressed", String(active));
21
+ });
22
+ });
23
+ }
24
+
25
+ // js/modules/navigation.js
26
+ var STORAGE_KEY = `${prefix}sidebar`;
27
+ var documentListenersAdded = false;
28
+ function initNavigation() {
29
+ const shellClass = `${prefix}shell`;
30
+ const navbarPrefix = `${prefix}navbar`;
31
+ const stored = localStorage.getItem(STORAGE_KEY);
32
+ if (stored === "open") {
33
+ document.querySelectorAll(`.${shellClass}`).forEach((shell) => {
34
+ shell.dataset.sidebar = "open";
35
+ });
36
+ }
37
+ document.querySelectorAll(`[data-${prefix}sidebar-toggle]`).forEach((btn) => {
38
+ if (btn._lnNav) return;
39
+ btn._lnNav = true;
40
+ btn.addEventListener("click", () => {
41
+ const shell = btn.closest(`.${shellClass}`);
42
+ if (!shell) return;
43
+ const isOpen = shell.dataset.sidebar === "open";
44
+ if (isOpen) {
45
+ delete shell.dataset.sidebar;
46
+ localStorage.setItem(STORAGE_KEY, "closed");
47
+ } else {
48
+ shell.dataset.sidebar = "open";
49
+ localStorage.setItem(STORAGE_KEY, "open");
50
+ }
51
+ });
52
+ });
53
+ document.querySelectorAll(`.${navbarPrefix}--sticky.${navbarPrefix}--transparent`).forEach((navbar) => {
54
+ if (navbar._lnNav) return;
55
+ navbar._lnNav = true;
56
+ const sentinel = document.createElement("div");
57
+ sentinel.style.cssText = "position:absolute;top:0;height:1px;width:1px;pointer-events:none;";
58
+ navbar.parentElement.insertBefore(sentinel, navbar);
59
+ const observer2 = new IntersectionObserver(([entry]) => {
60
+ navbar.classList.toggle("is-stuck", !entry.isIntersecting);
61
+ }, { threshold: 0 });
62
+ observer2.observe(sentinel);
63
+ });
64
+ function closeNavbarPanel(navbar) {
65
+ const toggle = navbar.querySelector(`[data-${prefix}navbar-toggle]`);
66
+ const panel = navbar.querySelector(`.${navbarPrefix}__panel`);
67
+ if (toggle) toggle.setAttribute("aria-expanded", "false");
68
+ if (panel) panel.setAttribute("aria-hidden", "true");
69
+ document.documentElement.classList.remove(`${prefix}navbar-open`);
70
+ }
71
+ function openNavbarPanel(navbar) {
72
+ const toggle = navbar.querySelector(`[data-${prefix}navbar-toggle]`);
73
+ const panel = navbar.querySelector(`.${navbarPrefix}__panel`);
74
+ if (toggle) toggle.setAttribute("aria-expanded", "true");
75
+ if (panel) panel.setAttribute("aria-hidden", "false");
76
+ document.documentElement.classList.add(`${prefix}navbar-open`);
77
+ }
78
+ document.querySelectorAll(`[data-${prefix}navbar-toggle]`).forEach((btn) => {
79
+ if (btn._lnNav) return;
80
+ btn._lnNav = true;
81
+ btn.addEventListener("click", () => {
82
+ const navbar = btn.closest(`.${navbarPrefix}`);
83
+ if (!navbar) return;
84
+ const expanded = btn.getAttribute("aria-expanded") === "true";
85
+ if (expanded) {
86
+ closeNavbarPanel(navbar);
87
+ } else {
88
+ openNavbarPanel(navbar);
89
+ }
90
+ });
91
+ });
92
+ if (documentListenersAdded) return;
93
+ documentListenersAdded = true;
94
+ document.addEventListener("click", (e) => {
95
+ document.querySelectorAll(`.${navbarPrefix}__panel[aria-hidden="false"]`).forEach((panel) => {
96
+ const navbar = panel.closest(`.${navbarPrefix}`);
97
+ if (navbar && !navbar.contains(e.target)) {
98
+ closeNavbarPanel(navbar);
99
+ }
100
+ });
101
+ });
102
+ document.addEventListener("keydown", (e) => {
103
+ if (e.key !== "Escape") return;
104
+ document.querySelectorAll(`.${navbarPrefix}__panel[aria-hidden="false"]`).forEach((panel) => {
105
+ const navbar = panel.closest(`.${navbarPrefix}`);
106
+ if (navbar) closeNavbarPanel(navbar);
107
+ });
108
+ });
109
+ }
110
+
111
+ // js/modules/dialog.js
112
+ var ATTR2 = `data-${prefix}dialog`;
113
+ var CLOSE_ATTR = `data-${prefix}dialog-close`;
114
+ function initDialog() {
115
+ document.querySelectorAll(`[${ATTR2}]`).forEach((trigger) => {
116
+ if (trigger._lnDialog) return;
117
+ const d = new Dialog(trigger);
118
+ d.init();
119
+ });
120
+ }
121
+ var Dialog = class {
122
+ constructor(trigger) {
123
+ this.trigger = trigger;
124
+ this.targetId = trigger.getAttribute(ATTR2);
125
+ this.dialog = null;
126
+ }
127
+ init() {
128
+ this.trigger._lnDialog = true;
129
+ this.dialog = document.getElementById(this.targetId);
130
+ if (!this.dialog) return;
131
+ this.trigger.addEventListener("click", () => this.open());
132
+ this.dialog.addEventListener("click", (e) => {
133
+ if (e.target === this.dialog) this.close();
134
+ });
135
+ this.dialog.querySelectorAll(`[${CLOSE_ATTR}]`).forEach((btn) => {
136
+ btn.addEventListener("click", () => this.close());
137
+ });
138
+ this.dialog.addEventListener("close", () => {
139
+ this.dialog.dispatchEvent(
140
+ new CustomEvent(`${prefix}dialog:close`, { bubbles: true })
141
+ );
142
+ });
143
+ }
144
+ open() {
145
+ if (!this.dialog) return;
146
+ this.dialog.showModal();
147
+ this.dialog.dispatchEvent(
148
+ new CustomEvent(`${prefix}dialog:open`, { bubbles: true })
149
+ );
150
+ }
151
+ close() {
152
+ if (!this.dialog) return;
153
+ this.dialog.close();
154
+ }
155
+ };
156
+
157
+ // js/modules/dropdown.js
158
+ var TOGGLE = `${prefix}dropdown__toggle`;
159
+ var MENU = `${prefix}dropdown__menu`;
160
+ var ITEM = `${prefix}dropdown__item`;
161
+ function initDropdown() {
162
+ document.querySelectorAll(`[data-${prefix}dropdown]`).forEach((root) => {
163
+ if (root._lnDropdown) return;
164
+ root._lnDropdown = true;
165
+ const dd = new Dropdown(root);
166
+ dd.init();
167
+ });
168
+ }
169
+ var Dropdown = class {
170
+ constructor(root) {
171
+ this.root = root;
172
+ this.toggle = root.querySelector(`.${TOGGLE}`);
173
+ this.menu = root.querySelector(`.${MENU}`);
174
+ this.items = [];
175
+ this.activeIndex = -1;
176
+ }
177
+ init() {
178
+ if (!this.toggle || !this.menu) return;
179
+ this.items = [...this.menu.querySelectorAll(`.${ITEM}:not([disabled])`)];
180
+ this.toggle.addEventListener("keydown", (e) => {
181
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
182
+ e.preventDefault();
183
+ this.open();
184
+ this.focusItem(0);
185
+ }
186
+ });
187
+ this.menu.addEventListener("keydown", (e) => this.onMenuKeydown(e));
188
+ this.menu.addEventListener("toggle", (e) => {
189
+ if (e.newState === "closed") {
190
+ this.activeIndex = -1;
191
+ this.clearActive();
192
+ }
193
+ });
194
+ }
195
+ open() {
196
+ if (this.menu.matches?.(":popover-open")) return;
197
+ this.menu.showPopover?.();
198
+ }
199
+ close() {
200
+ this.menu.hidePopover?.();
201
+ this.toggle.focus();
202
+ }
203
+ focusItem(index) {
204
+ this.activeIndex = Math.max(0, Math.min(index, this.items.length - 1));
205
+ this.items.forEach((item, i) => {
206
+ item.toggleAttribute("data-active", i === this.activeIndex);
207
+ });
208
+ this.items[this.activeIndex]?.focus();
209
+ }
210
+ clearActive() {
211
+ this.items.forEach((item) => {
212
+ item.removeAttribute("data-active");
213
+ });
214
+ }
215
+ onMenuKeydown(e) {
216
+ switch (e.key) {
217
+ case "ArrowDown":
218
+ e.preventDefault();
219
+ this.focusItem(this.activeIndex + 1);
220
+ break;
221
+ case "ArrowUp":
222
+ e.preventDefault();
223
+ this.focusItem(this.activeIndex - 1);
224
+ break;
225
+ case "Home":
226
+ e.preventDefault();
227
+ this.focusItem(0);
228
+ break;
229
+ case "End":
230
+ e.preventDefault();
231
+ this.focusItem(this.items.length - 1);
232
+ break;
233
+ case "Escape":
234
+ e.preventDefault();
235
+ this.close();
236
+ break;
237
+ default:
238
+ if (e.key.length === 1) {
239
+ const char = e.key.toLowerCase();
240
+ const start = (this.activeIndex + 1) % this.items.length;
241
+ for (let i = 0; i < this.items.length; i++) {
242
+ const idx = (start + i) % this.items.length;
243
+ const text = this.items[idx].textContent.trim().toLowerCase();
244
+ if (text.startsWith(char)) {
245
+ this.focusItem(idx);
246
+ break;
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ };
253
+
254
+ // js/modules/tooltip.js
255
+ var ATTR3 = `data-${prefix}tooltip`;
256
+ var BLOCK = `${prefix}tooltip`;
257
+ var idCounter = 0;
258
+ function initTooltip() {
259
+ document.querySelectorAll(`[${ATTR3}]`).forEach((trigger) => {
260
+ if (trigger._lnTooltip) return;
261
+ const t = new Tooltip(trigger);
262
+ t.init();
263
+ });
264
+ }
265
+ var Tooltip = class {
266
+ constructor(trigger) {
267
+ this.trigger = trigger;
268
+ this.tooltip = null;
269
+ this.showTimeout = null;
270
+ this.id = `${BLOCK}-${++idCounter}`;
271
+ }
272
+ init() {
273
+ this.trigger._lnTooltip = true;
274
+ this.trigger.addEventListener("mouseenter", () => this.scheduleShow());
275
+ this.trigger.addEventListener("focusin", () => this.scheduleShow());
276
+ this.trigger.addEventListener("mouseleave", () => this.hide());
277
+ this.trigger.addEventListener("focusout", () => this.hide());
278
+ this.trigger.addEventListener("keydown", (e) => {
279
+ if (e.key === "Escape") this.hide();
280
+ });
281
+ }
282
+ scheduleShow() {
283
+ if (this.showTimeout) return;
284
+ this.showTimeout = setTimeout(() => this.show(), 200);
285
+ }
286
+ show() {
287
+ if (this.tooltip) return;
288
+ const text = this.trigger.getAttribute(ATTR3);
289
+ if (!text) return;
290
+ const position = this.trigger.getAttribute(`${ATTR3}-position`) || "top";
291
+ this.tooltip = document.createElement("div");
292
+ this.tooltip.className = `${BLOCK} ${BLOCK}--${position}`;
293
+ this.tooltip.id = this.id;
294
+ this.tooltip.setAttribute("role", "tooltip");
295
+ this.tooltip.textContent = text;
296
+ const arrow = document.createElement("div");
297
+ arrow.className = `${BLOCK}__arrow`;
298
+ this.tooltip.appendChild(arrow);
299
+ document.body.appendChild(this.tooltip);
300
+ this.trigger.setAttribute("aria-describedby", this.id);
301
+ this.position(position);
302
+ }
303
+ hide() {
304
+ if (this.showTimeout) {
305
+ clearTimeout(this.showTimeout);
306
+ this.showTimeout = null;
307
+ }
308
+ if (this.tooltip) {
309
+ this.tooltip.remove();
310
+ this.tooltip = null;
311
+ this.trigger.removeAttribute("aria-describedby");
312
+ }
313
+ }
314
+ position(placement) {
315
+ if (!this.tooltip) return;
316
+ const triggerRect = this.trigger.getBoundingClientRect();
317
+ const tooltipRect = this.tooltip.getBoundingClientRect();
318
+ const gap = 8;
319
+ let top, left;
320
+ switch (placement) {
321
+ case "bottom":
322
+ top = triggerRect.bottom + gap;
323
+ left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
324
+ break;
325
+ case "start":
326
+ top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
327
+ left = triggerRect.left - tooltipRect.width - gap;
328
+ break;
329
+ case "end":
330
+ top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
331
+ left = triggerRect.right + gap;
332
+ break;
333
+ default:
334
+ top = triggerRect.top - tooltipRect.height - gap;
335
+ left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
336
+ }
337
+ this.tooltip.style.top = `${top + window.scrollY}px`;
338
+ this.tooltip.style.left = `${left + window.scrollX}px`;
339
+ }
340
+ };
341
+
342
+ // js/modules/tabs.js
343
+ var TAB = `${prefix}tabs__tab`;
344
+ var PANEL = `${prefix}tabs__panel`;
345
+ var LIST = `${prefix}tabs__list`;
346
+ function initTabs() {
347
+ document.querySelectorAll(`[data-${prefix}tabs]`).forEach((root) => {
348
+ if (root._lnTabs) return;
349
+ root._lnTabs = true;
350
+ const tabs = new Tabs(root);
351
+ tabs.init();
352
+ });
353
+ }
354
+ var Tabs = class {
355
+ constructor(root) {
356
+ this.root = root;
357
+ this.tablist = root.querySelector(`.${LIST}`);
358
+ this.tabs = [...root.querySelectorAll(`.${TAB}`)];
359
+ this.panels = [...root.querySelectorAll(`.${PANEL}`)];
360
+ this.manual = root.dataset[`${prefix}tabs`] === "manual" || root.getAttribute(`data-${prefix}tabs`) === "manual";
361
+ }
362
+ init() {
363
+ if (!this.tablist || this.tabs.length === 0) return;
364
+ this.tablist.setAttribute("role", "tablist");
365
+ this.tabs.forEach((tab, i) => {
366
+ tab.setAttribute("role", "tab");
367
+ const panelId = tab.getAttribute("aria-controls");
368
+ const panel = panelId ? document.getElementById(panelId) : this.panels[i];
369
+ if (panel) panel.setAttribute("role", "tabpanel");
370
+ if (tab.getAttribute("aria-selected") !== "true") {
371
+ tab.setAttribute("aria-selected", "false");
372
+ tab.setAttribute("tabindex", "-1");
373
+ if (panel) panel.hidden = true;
374
+ } else {
375
+ tab.setAttribute("tabindex", "0");
376
+ if (panel) panel.hidden = false;
377
+ }
378
+ });
379
+ if (!this.tabs.some((t) => t.getAttribute("aria-selected") === "true")) {
380
+ this.activate(this.tabs[0]);
381
+ }
382
+ this.tablist.addEventListener("keydown", (e) => this.onKeydown(e));
383
+ this.tablist.addEventListener("click", (e) => {
384
+ const tab = e.target.closest(`.${TAB}`);
385
+ if (tab) this.activate(tab);
386
+ });
387
+ const hashAttr = `data-${prefix}tabs-hash`;
388
+ if (this.root.hasAttribute(hashAttr)) {
389
+ this.syncFromHash();
390
+ window.addEventListener("hashchange", () => this.syncFromHash());
391
+ }
392
+ }
393
+ activate(tab) {
394
+ this.tabs.forEach((t, i) => {
395
+ const isActive = t === tab;
396
+ t.setAttribute("aria-selected", isActive ? "true" : "false");
397
+ t.setAttribute("tabindex", isActive ? "0" : "-1");
398
+ const panelId = t.getAttribute("aria-controls");
399
+ const panel = panelId ? document.getElementById(panelId) : this.panels[i];
400
+ if (panel) panel.hidden = !isActive;
401
+ });
402
+ tab.focus();
403
+ }
404
+ onKeydown(e) {
405
+ const idx = this.tabs.indexOf(e.target);
406
+ if (idx === -1) return;
407
+ let next;
408
+ switch (e.key) {
409
+ case "ArrowRight":
410
+ case "ArrowDown":
411
+ next = this.tabs[(idx + 1) % this.tabs.length];
412
+ break;
413
+ case "ArrowLeft":
414
+ case "ArrowUp":
415
+ next = this.tabs[(idx - 1 + this.tabs.length) % this.tabs.length];
416
+ break;
417
+ case "Home":
418
+ next = this.tabs[0];
419
+ break;
420
+ case "End":
421
+ next = this.tabs[this.tabs.length - 1];
422
+ break;
423
+ default:
424
+ return;
425
+ }
426
+ e.preventDefault();
427
+ if (this.manual) {
428
+ next.focus();
429
+ next.setAttribute("tabindex", "0");
430
+ e.target.setAttribute("tabindex", "-1");
431
+ } else {
432
+ this.activate(next);
433
+ }
434
+ }
435
+ syncFromHash() {
436
+ const hash = location.hash.slice(1);
437
+ const tab = this.tabs.find((t) => t.getAttribute("aria-controls") === hash);
438
+ if (tab) this.activate(tab);
439
+ }
440
+ };
441
+
442
+ // js/modules/toast.js
443
+ var BLOCK2 = `${prefix}toast`;
444
+ var REGION = `${prefix}toast-region`;
445
+ var region = null;
446
+ function initToast() {
447
+ if (!region) {
448
+ region = document.createElement("div");
449
+ region.className = `${REGION} ${REGION}--bottom-end`;
450
+ region.setAttribute("aria-live", "polite");
451
+ region.setAttribute("aria-relevant", "additions");
452
+ document.body.appendChild(region);
453
+ }
454
+ }
455
+
456
+ // js/modules/dismiss.js
457
+ var ATTR4 = `data-${prefix}dismiss`;
458
+ var DISMISSIBLE = `data-${prefix}dismissible`;
459
+ function initDismiss() {
460
+ document.querySelectorAll(`[${ATTR4}]`).forEach((trigger) => {
461
+ if (trigger._lnDismiss) return;
462
+ const d = new Dismiss(trigger);
463
+ d.init();
464
+ });
465
+ }
466
+ var Dismiss = class {
467
+ constructor(trigger) {
468
+ this.trigger = trigger;
469
+ }
470
+ init() {
471
+ this.trigger._lnDismiss = true;
472
+ this.trigger.addEventListener("click", () => this.dismiss());
473
+ }
474
+ dismiss() {
475
+ const selector = this.trigger.getAttribute(ATTR4);
476
+ let target;
477
+ if (selector) {
478
+ target = document.querySelector(selector);
479
+ } else {
480
+ target = this.trigger.closest(`[${DISMISSIBLE}]`);
481
+ }
482
+ if (!target) {
483
+ target = this.trigger.parentElement;
484
+ }
485
+ if (target) {
486
+ target.hidden = true;
487
+ target.dispatchEvent(new CustomEvent(`${prefix}dismiss`, { bubbles: true }));
488
+ }
489
+ }
490
+ };
491
+
492
+ // js/modules/quantity.js
493
+ var ATTR5 = `data-${prefix}quantity`;
494
+ var DEC = `${prefix}quantity__decrement`;
495
+ var INC = `${prefix}quantity__increment`;
496
+ var INPUT = `${prefix}quantity__input`;
497
+ function initQuantity() {
498
+ document.querySelectorAll(`[${ATTR5}]`).forEach((root) => {
499
+ if (root._lnQuantity) return;
500
+ const q = new Quantity(root);
501
+ q.init();
502
+ });
503
+ }
504
+ var Quantity = class {
505
+ constructor(root) {
506
+ this.root = root;
507
+ this.input = root.querySelector(`.${INPUT}`);
508
+ this.decBtn = root.querySelector(`.${DEC}`);
509
+ this.incBtn = root.querySelector(`.${INC}`);
510
+ }
511
+ init() {
512
+ if (!this.input) return;
513
+ this.root._lnQuantity = true;
514
+ this.decBtn?.addEventListener("click", () => this.step(-1));
515
+ this.incBtn?.addEventListener("click", () => this.step(1));
516
+ this.input.addEventListener("change", () => this.clamp());
517
+ this.updateButtons();
518
+ }
519
+ get min() {
520
+ return Number(this.input.min) || 0;
521
+ }
522
+ get max() {
523
+ return Number(this.input.max) || Infinity;
524
+ }
525
+ get stepVal() {
526
+ return Number(this.input.step) || 1;
527
+ }
528
+ get value() {
529
+ return Number(this.input.value) || 0;
530
+ }
531
+ step(direction) {
532
+ const next = this.value + this.stepVal * direction;
533
+ this.setValue(next);
534
+ }
535
+ clamp() {
536
+ let val = this.value;
537
+ if (val < this.min) val = this.min;
538
+ if (val > this.max) val = this.max;
539
+ this.setValue(val);
540
+ }
541
+ setValue(val) {
542
+ val = Math.max(this.min, Math.min(val, this.max));
543
+ this.input.value = val;
544
+ this.input.dispatchEvent(new Event("change", { bubbles: true }));
545
+ this.updateButtons();
546
+ }
547
+ updateButtons() {
548
+ if (this.decBtn) this.decBtn.disabled = this.value <= this.min;
549
+ if (this.incBtn) this.incBtn.disabled = this.value >= this.max;
550
+ }
551
+ };
552
+
553
+ // js/modules/gallery.js
554
+ var ATTR6 = `data-${prefix}gallery`;
555
+ var MAIN = `${prefix}gallery__main`;
556
+ var SLIDE = `${prefix}gallery__slide`;
557
+ var THUMB = `${prefix}gallery__thumb`;
558
+ var DOT = `${prefix}gallery__dot`;
559
+ function initGallery() {
560
+ document.querySelectorAll(`[${ATTR6}]`).forEach((root) => {
561
+ if (root._lnGallery) return;
562
+ const g = new Gallery(root);
563
+ g.init();
564
+ });
565
+ }
566
+ var Gallery = class {
567
+ constructor(root) {
568
+ this.root = root;
569
+ this.main = root.querySelector(`.${MAIN}`);
570
+ this.slides = [...root.querySelectorAll(`.${SLIDE}`)];
571
+ this.thumbs = [...root.querySelectorAll(`.${THUMB}`)];
572
+ this.dots = [...root.querySelectorAll(`.${DOT}`)];
573
+ this.activeIndex = 0;
574
+ }
575
+ init() {
576
+ if (!this.main || this.slides.length === 0) return;
577
+ this.root._lnGallery = true;
578
+ const observer2 = new IntersectionObserver(
579
+ (entries) => {
580
+ for (const entry of entries) {
581
+ if (entry.isIntersecting) {
582
+ const index = this.slides.indexOf(entry.target);
583
+ if (index !== -1) this.setActive(index);
584
+ }
585
+ }
586
+ },
587
+ { root: this.main, threshold: 0.5 }
588
+ );
589
+ this.slides.forEach((slide) => observer2.observe(slide));
590
+ this.thumbs.forEach((thumb, i) => {
591
+ thumb.addEventListener("click", () => this.goTo(i));
592
+ });
593
+ this.dots.forEach((dot, i) => {
594
+ dot.addEventListener("click", () => this.goTo(i));
595
+ });
596
+ this.main.addEventListener("keydown", (e) => {
597
+ if (e.key === "ArrowRight") {
598
+ e.preventDefault();
599
+ this.goTo(this.activeIndex + 1);
600
+ } else if (e.key === "ArrowLeft") {
601
+ e.preventDefault();
602
+ this.goTo(this.activeIndex - 1);
603
+ }
604
+ });
605
+ this.setActive(0);
606
+ }
607
+ goTo(index) {
608
+ index = Math.max(0, Math.min(index, this.slides.length - 1));
609
+ this.slides[index]?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
610
+ }
611
+ setActive(index) {
612
+ this.activeIndex = index;
613
+ this.thumbs.forEach((thumb, i) => {
614
+ thumb.classList.toggle("is-active", i === index);
615
+ thumb.setAttribute("aria-selected", i === index ? "true" : "false");
616
+ });
617
+ this.dots.forEach((dot, i) => {
618
+ dot.classList.toggle("is-active", i === index);
619
+ dot.setAttribute("aria-selected", i === index ? "true" : "false");
620
+ });
621
+ }
622
+ };
623
+
624
+ // js/modules/copy.js
625
+ var ATTR7 = `data-${prefix}copy`;
626
+ var TARGET_ATTR = `data-${prefix}copy-target`;
627
+ var VALUE_ATTR = `data-${prefix}copy-value`;
628
+ function initCopy() {
629
+ document.querySelectorAll(`[${ATTR7}]`).forEach((btn) => {
630
+ if (btn._lnCopy) return;
631
+ const c = new Copy(btn);
632
+ c.init();
633
+ });
634
+ }
635
+ var Copy = class {
636
+ constructor(trigger) {
637
+ this.trigger = trigger;
638
+ this.originalText = trigger.textContent;
639
+ }
640
+ init() {
641
+ this.trigger._lnCopy = true;
642
+ this.trigger.addEventListener("click", () => this.copy());
643
+ }
644
+ async copy() {
645
+ const value = this.getValue();
646
+ if (!value) return;
647
+ try {
648
+ await navigator.clipboard.writeText(value);
649
+ this.showCopied();
650
+ this.trigger.dispatchEvent(
651
+ new CustomEvent(`${prefix}copy`, { bubbles: true, detail: { value } })
652
+ );
653
+ } catch {
654
+ }
655
+ }
656
+ getValue() {
657
+ const value = this.trigger.getAttribute(VALUE_ATTR);
658
+ if (value) return value;
659
+ const targetSelector = this.trigger.getAttribute(TARGET_ATTR);
660
+ if (targetSelector) {
661
+ const target = document.querySelector(targetSelector);
662
+ return target?.textContent ?? "";
663
+ }
664
+ return "";
665
+ }
666
+ showCopied() {
667
+ this.trigger.setAttribute("data-copied", "");
668
+ const copiedText = this.trigger.getAttribute(`data-${prefix}copy-text`) || "Copied!";
669
+ this.trigger.textContent = copiedText;
670
+ setTimeout(() => {
671
+ this.trigger.removeAttribute("data-copied");
672
+ this.trigger.textContent = this.originalText;
673
+ }, 2e3);
674
+ }
675
+ };
676
+
677
+ // js/modules/table.js
678
+ var ATTR8 = `data-${prefix}table`;
679
+ var SORT = `${prefix}table__sort`;
680
+ var SELECT = `${prefix}table__select`;
681
+ function initTable() {
682
+ document.querySelectorAll(`[${ATTR8}]`).forEach((root) => {
683
+ if (root._lnTable) return;
684
+ const t = new Table(root);
685
+ t.init();
686
+ });
687
+ }
688
+ var Table = class {
689
+ constructor(root) {
690
+ this.root = root;
691
+ this.table = root.querySelector("table") || root;
692
+ this.tbody = this.table.querySelector("tbody");
693
+ }
694
+ init() {
695
+ this.root._lnTable = true;
696
+ this.initSort();
697
+ this.initSelectAll();
698
+ }
699
+ initSort() {
700
+ this.root.querySelectorAll(`.${SORT}`).forEach((btn) => {
701
+ btn.addEventListener("click", () => {
702
+ const key = btn.getAttribute("data-sort-key");
703
+ if (!key || !this.tbody) return;
704
+ const current = btn.getAttribute("aria-sort");
705
+ const direction = current === "ascending" ? "descending" : "ascending";
706
+ this.root.querySelectorAll(`.${SORT}`).forEach((b) => {
707
+ b.removeAttribute("aria-sort");
708
+ });
709
+ btn.setAttribute("aria-sort", direction);
710
+ const colIndex = this.getColumnIndex(btn);
711
+ if (colIndex === -1) return;
712
+ const rows = [...this.tbody.querySelectorAll("tr")];
713
+ rows.sort((a, b) => {
714
+ const aVal = a.cells[colIndex]?.textContent.trim() ?? "";
715
+ const bVal = b.cells[colIndex]?.textContent.trim() ?? "";
716
+ const aNum = parseFloat(aVal);
717
+ const bNum = parseFloat(bVal);
718
+ let cmp;
719
+ if (!isNaN(aNum) && !isNaN(bNum)) {
720
+ cmp = aNum - bNum;
721
+ } else {
722
+ cmp = aVal.localeCompare(bVal);
723
+ }
724
+ return direction === "ascending" ? cmp : -cmp;
725
+ });
726
+ rows.forEach((row) => this.tbody.appendChild(row));
727
+ this.root.dispatchEvent(
728
+ new CustomEvent(`${prefix}table:sort`, {
729
+ bubbles: true,
730
+ detail: { key, direction }
731
+ })
732
+ );
733
+ });
734
+ });
735
+ }
736
+ getColumnIndex(btn) {
737
+ const th = btn.closest("th");
738
+ if (!th) return -1;
739
+ return [...th.parentElement.children].indexOf(th);
740
+ }
741
+ initSelectAll() {
742
+ const selectAll = this.root.querySelector(`th .${SELECT}`);
743
+ if (!selectAll || !this.tbody) return;
744
+ const rowCheckboxes = () => this.tbody.querySelectorAll(`.${SELECT}`);
745
+ selectAll.addEventListener("change", () => {
746
+ rowCheckboxes().forEach((cb) => {
747
+ cb.checked = selectAll.checked;
748
+ });
749
+ this.root.dispatchEvent(
750
+ new CustomEvent(`${prefix}table:select`, {
751
+ bubbles: true,
752
+ detail: { all: selectAll.checked }
753
+ })
754
+ );
755
+ });
756
+ this.tbody.addEventListener("change", (e) => {
757
+ if (!e.target.classList.contains(SELECT)) return;
758
+ const boxes = [...rowCheckboxes()];
759
+ const allChecked = boxes.every((cb) => cb.checked);
760
+ const someChecked = boxes.some((cb) => cb.checked);
761
+ selectAll.checked = allChecked;
762
+ selectAll.indeterminate = someChecked && !allChecked;
763
+ });
764
+ }
765
+ };
766
+
767
+ // js/modules/highlight.js
768
+ var ATTR9 = `data-${prefix}highlight`;
769
+ function initHighlight() {
770
+ document.querySelectorAll(`[${ATTR9}]`).forEach((block) => {
771
+ if (block._lnHighlight) return;
772
+ const h = new Highlight(block);
773
+ h.init();
774
+ });
775
+ }
776
+ var Highlight = class {
777
+ constructor(block) {
778
+ this.block = block;
779
+ this.code = block.querySelector("code") || block;
780
+ this.lang = block.getAttribute(ATTR9) || this.detectLanguage();
781
+ }
782
+ init() {
783
+ this.block._lnHighlight = true;
784
+ if (typeof window.Prism !== "undefined" && window.Prism.highlightElement) {
785
+ if (this.lang) this.code.classList.add(`language-${this.lang}`);
786
+ window.Prism.highlightElement(this.code);
787
+ return;
788
+ }
789
+ if (typeof window.hljs !== "undefined" && window.hljs.highlightElement) {
790
+ if (this.lang) this.code.classList.add(this.lang);
791
+ window.hljs.highlightElement(this.code);
792
+ return;
793
+ }
794
+ const raw = this.code.textContent;
795
+ const highlighted = this.tokenize(raw, this.lang);
796
+ this.code.innerHTML = highlighted;
797
+ }
798
+ detectLanguage() {
799
+ const cls = this.code.className;
800
+ const match = cls.match(/language-(\w+)/);
801
+ return match ? match[1] : null;
802
+ }
803
+ tokenize(src, lang) {
804
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
805
+ const rules = {
806
+ html: [
807
+ { pattern: /<!--[\s\S]*?-->/g, cls: "comment" },
808
+ { pattern: /(<\/?)([\w-]+)/g, replace: (_, bracket, tag) => `${esc(bracket)}<span class="hl-tag">${esc(tag)}</span>` },
809
+ { pattern: /([\w-]+)(=)(".*?")/g, replace: (_, attr, eq, val) => `<span class="hl-attr">${esc(attr)}</span>${esc(eq)}<span class="hl-string">${esc(val)}</span>` }
810
+ ],
811
+ css: [
812
+ { pattern: /\/\*[\s\S]*?\*\//g, cls: "comment" },
813
+ { pattern: /"[^"]*"|'[^']*'/g, cls: "string" },
814
+ { pattern: /[\w-]+(?=\s*:)/g, cls: "attr" },
815
+ { pattern: /#[0-9a-fA-F]{3,8}\b/g, cls: "number" },
816
+ { pattern: /\b\d+(\.\d+)?(px|em|rem|%|vh|vw|s|ms)?\b/g, cls: "number" }
817
+ ],
818
+ js: [
819
+ { pattern: /\/\/.*$/gm, cls: "comment" },
820
+ { pattern: /\/\*[\s\S]*?\*\//g, cls: "comment" },
821
+ { pattern: /"[^"]*"|'[^']*'|`[^`]*`/g, cls: "string" },
822
+ { pattern: /\b(const|let|var|function|return|if|else|for|while|class|import|export|from|new|this|true|false|null|undefined|async|await)\b/g, cls: "keyword" },
823
+ { pattern: /\b\d+(\.\d+)?\b/g, cls: "number" }
824
+ ]
825
+ };
826
+ const langRules = rules[lang];
827
+ if (!langRules) return esc(src);
828
+ let result = esc(src);
829
+ for (const rule of langRules) {
830
+ if (rule.replace) {
831
+ result = result.replace(rule.pattern, rule.replace);
832
+ } else {
833
+ result = result.replace(
834
+ rule.pattern,
835
+ (match) => `<span class="hl-${rule.cls}">${match}</span>`
836
+ );
837
+ }
838
+ }
839
+ return result;
840
+ }
841
+ };
842
+
843
+ // js/index.js
844
+ function init() {
845
+ initRhythm();
846
+ initNavigation();
847
+ initDialog();
848
+ initDropdown();
849
+ initTooltip();
850
+ initTabs();
851
+ initToast();
852
+ initDismiss();
853
+ initQuantity();
854
+ initGallery();
855
+ initCopy();
856
+ initTable();
857
+ initHighlight();
858
+ }
859
+ if (document.readyState === "loading") {
860
+ document.addEventListener("DOMContentLoaded", init);
861
+ } else {
862
+ init();
863
+ }
864
+ var pending = false;
865
+ var observer = new MutationObserver(() => {
866
+ if (pending) return;
867
+ pending = true;
868
+ queueMicrotask(() => {
869
+ pending = false;
870
+ init();
871
+ });
872
+ });
873
+ observer.observe(document.body, { childList: true, subtree: true });