panelset 1.0.7 → 1.2.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/panelset.js CHANGED
@@ -1,432 +1 @@
1
- const h = class h {
2
- constructor(t, i = {}) {
3
- this._openCloseGeneration = 0, this._isLoadingAsync = !1;
4
- let e;
5
- if (typeof t == "string") {
6
- if (e = document.querySelector(t), !e)
7
- throw new Error(`PanelSet: No element found for selector "${t}"`);
8
- } else
9
- e = t;
10
- if (this.element = e, h._validateElement(e), e.panelSet || e.dataset.panelset === "true")
11
- return console.warn("PanelSet: Element already initialized, returning existing instance"), e.panelSet;
12
- e.panelSet = this;
13
- const l = h._getDataConfig(e);
14
- if (this.config = h._mergeConfig(
15
- h.defaults,
16
- l,
17
- i
18
- ), this.panels = Array.from(e.querySelectorAll('[role="tabpanel"]')), this.panels.length === 0) {
19
- const s = `<${e.tagName.toLowerCase()}${e.id ? ` id="${e.id}"` : ""}${e.className ? ` class="${e.className}"` : ""}>`;
20
- console.error(`PanelSet: No panels with [role="tabpanel"] found inside ${s}. Make sure panel elements are children of the [data-panelset] element.`), this.panels = [], this.activePanel = null, this.panelWrapper = null, this.pendingPanel = null;
21
- return;
22
- }
23
- this.activePanel = this.panels.find((s) => s.classList.contains("active")) || this.panels[0], this.panelWrapper = this.element.querySelector(".panel-wrapper") || this._autoWrapPanels(), this.pendingPanel = this.activePanel, this.element.dataset.panelset = "true", this._log(`Initialized (${this.panels.length} panels)`), this._dispatch("ps:ready", { container: this.element, instance: this }), this._internalInit(), this._syncAriaWithActivePanel();
24
- let o;
25
- window.addEventListener("resize", () => {
26
- clearTimeout(o), o = setTimeout(() => {
27
- this._updateHighestPanel();
28
- }, 250);
29
- });
30
- }
31
- // Parse data attributes from element
32
- static _getDataConfig(t) {
33
- const i = t.dataset, e = {}, l = {
34
- transitions: "json",
35
- closable: "boolean",
36
- emptyPanelHeight: "number",
37
- loadingDelay: "number",
38
- debug: "boolean"
39
- };
40
- for (const [o, s] of Object.entries(l)) {
41
- if (!(o in i)) continue;
42
- const n = i[o];
43
- if (n !== void 0)
44
- switch (s) {
45
- case "boolean":
46
- e[o] = n !== "false";
47
- break;
48
- case "number":
49
- e[o] = parseInt(n, 10);
50
- break;
51
- case "json":
52
- try {
53
- e[o] = JSON.parse(n);
54
- } catch {
55
- e[o] = n !== "false";
56
- }
57
- break;
58
- }
59
- }
60
- return e;
61
- }
62
- // Merge configurations
63
- static _mergeConfig(t, i, e) {
64
- return {
65
- ...t,
66
- ...i,
67
- ...e
68
- };
69
- }
70
- /**
71
- * Initialize PanelSet instances
72
- * @param selectorOrOptions - CSS selector string or config object
73
- * @param options - Additional config options (when first param is selector)
74
- * @returns Array of PanelSet instances
75
- */
76
- static init(t = {}, i = {}) {
77
- let e, l;
78
- typeof t == "string" ? (e = t, l = i) : (l = t, e = l.selector || "[data-panelset]");
79
- const o = document.querySelectorAll(e), s = [];
80
- return o.forEach((n) => {
81
- try {
82
- h._validateElement(n);
83
- } catch (r) {
84
- console.error(r.message);
85
- return;
86
- }
87
- if (n.panelSet || n.dataset.panelset === "true") {
88
- n.panelSet && s.push(n.panelSet);
89
- return;
90
- }
91
- const a = new h(n, l);
92
- s.push(a);
93
- }), s;
94
- }
95
- // Debug logging helper
96
- _log(t) {
97
- if (!this.config.debug) return;
98
- const i = this.element.id || "no id";
99
- console.log(`[PanelSet] - "${i}" -`, t);
100
- }
101
- static _validateElement(t) {
102
- if (!t.hasAttribute("data-panelset")) {
103
- const i = `<${t.tagName.toLowerCase()}${t.id ? ` id="${t.id}"` : ""}${t.className ? ` class="${t.className}"` : ""}>`;
104
- throw new Error(`PanelSet: Element is missing required data-panelset attribute. Element: ${i}`);
105
- }
106
- }
107
- _autoWrapPanels() {
108
- const t = document.createElement("div");
109
- return t.className = "panel-wrapper", this.panels.forEach((i) => t.appendChild(i)), this.element.appendChild(t), t;
110
- }
111
- _internalInit() {
112
- this.panels.forEach((t) => {
113
- t.classList.remove("fade", "incoming"), t !== this.activePanel ? (t.hidden = !0, t.classList.remove("active")) : (t.hidden = !1, t.classList.add("active"));
114
- }), this.element.style.height = "", this._updateHighestPanel();
115
- }
116
- // Dispatch custom event helper
117
- _dispatch(t, i) {
118
- this.element.dispatchEvent(
119
- new CustomEvent(t, {
120
- detail: i,
121
- bubbles: !0,
122
- cancelable: !1
123
- })
124
- );
125
- }
126
- /* --- Modular helpers --- */
127
- _getVerticalMetrics(t) {
128
- if (!t) return 0;
129
- const i = getComputedStyle(t);
130
- return ["paddingTop", "paddingBottom", "borderTopWidth", "borderBottomWidth"].reduce((e, l) => e + (parseFloat(i[l]) || 0), 0);
131
- }
132
- _measureHeight(t) {
133
- let i = t.offsetHeight;
134
- return i += this._getVerticalMetrics(this.panelWrapper), i += this._getVerticalMetrics(this.element), i;
135
- }
136
- _updateHighestPanel() {
137
- if (this.element.hasAttribute("data-ps-track-height")) {
138
- console.warn("PanelSet: data-ps-track-height should be placed on a parent element, not on [data-panelset] itself. Height tracking skipped.");
139
- return;
140
- }
141
- const t = this.element.closest("[data-ps-track-height]");
142
- if (!t) return;
143
- let i = 0;
144
- this.panels.forEach((e) => {
145
- const l = e.hidden, o = e.classList.contains("active");
146
- this.panels.forEach((n) => {
147
- n.hidden = !0, n.classList.remove("active");
148
- }), e.hidden = !1, e.classList.add("active"), e.style.visibility = "hidden", this.element.offsetHeight;
149
- const s = this.element.offsetHeight;
150
- s > i && (i = s), e.hidden = l, o || e.classList.remove("active"), e.style.visibility = "";
151
- }), this.activePanel && (this.activePanel.hidden = !1, this.activePanel.classList.add("active")), this.panels.forEach((e) => {
152
- e !== this.activePanel && (e.hidden = !0, e.classList.remove("active"));
153
- }), t.style.setProperty("--ps-max-height", `${i}px`), this._log(`Max container height: ${i}px (set on tracking parent)`);
154
- }
155
- _cleanupPanels(t) {
156
- this.panels.forEach((i) => {
157
- i.classList.remove("fade", "incoming"), i !== t ? (i.classList.remove("active"), i.hidden = !0) : (i.classList.add("active"), i.hidden = !1);
158
- }), this.element.style.height = "", this.element.classList.remove("is-transitioning"), this.activePanel = t;
159
- }
160
- _waitForTransition(t) {
161
- return new Promise((i) => {
162
- const e = getComputedStyle(t), l = parseFloat(e.transitionDuration) || 0, o = parseFloat(e.transitionDelay) || 0;
163
- if (l + o === 0) {
164
- i();
165
- return;
166
- }
167
- const s = (n) => {
168
- n.target === t && (t.removeEventListener("transitionend", s), i());
169
- };
170
- t.addEventListener("transitionend", s);
171
- });
172
- }
173
- _handleAutoFocus(t, i, e) {
174
- if (!(i === "input") && i && e && !(e.type.startsWith("key") || e instanceof MouseEvent && e.detail === 0)) {
175
- this._log("Skipping autofocus (touch/mouse interaction)");
176
- return;
177
- }
178
- const o = (s, n) => {
179
- setTimeout(() => {
180
- s.focus(), this._log(n);
181
- }, 100);
182
- };
183
- if (i === !0)
184
- t.hasAttribute("tabindex") || t.setAttribute("tabindex", "-1"), o(t, `Auto-focused panel: ${t.id}`);
185
- else if (i === "heading") {
186
- const s = t.querySelector("h1, h2, h3, h4, h5, h6");
187
- s && (s.hasAttribute("tabindex") || s.setAttribute("tabindex", "-1"), o(s, `Auto-focused heading in: ${t.id}`));
188
- } else if (i === "first") {
189
- const s = t.querySelector('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
190
- s && o(s, `Auto-focused first element in: ${t.id}`);
191
- } else if (i === "input") {
192
- const s = t.querySelector('input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled])');
193
- s && o(s, `Auto-focused input in: ${t.id}`);
194
- } else typeof i == "function" && (i(t), this._log(`Auto-focused (custom) in: ${t.id}`));
195
- }
196
- _syncAriaWithActivePanel() {
197
- if (!this.activePanel?.id) return;
198
- const t = document.querySelector(
199
- `[role="tab"][aria-controls="${this.activePanel.id}"]`
200
- );
201
- if (!t)
202
- return;
203
- const i = t.closest('[role="tablist"]');
204
- i && (i.querySelectorAll('[role="tab"]').forEach((l) => {
205
- l.setAttribute("aria-selected", "false");
206
- }), t.setAttribute("aria-selected", "true"));
207
- }
208
- // Shared helper for open/close
209
- _animateOpenClose(t, i) {
210
- const e = t ? "opening" : "closing", o = `is-${t ? "closing" : "opening"}`, s = `is-${e}`;
211
- this._log(t ? "Opening" : "Closing"), this._openCloseGeneration++;
212
- const n = this._openCloseGeneration;
213
- this.element.classList.contains(o) && this.element.classList.remove(o);
214
- const a = t ? this._measureHeight(this.pendingPanel) : 0;
215
- if (i && this.config.transitions) {
216
- this.element.classList.add(s);
217
- const r = this.element.offsetHeight;
218
- this.element.style.height = `${r}px`, t && this.element.classList.remove("is-closed"), requestAnimationFrame(() => {
219
- this.element.style.height = `${a}px`, this._waitForTransition(this.element).then(() => {
220
- this._openCloseGeneration === n && (this.element.style.height = "", this.element.classList.remove(s), t || this.element.classList.add("is-closed"));
221
- });
222
- });
223
- } else
224
- t ? this.element.classList.remove("is-closed") : this.element.classList.add("is-closed"), this.element.style.height = "";
225
- }
226
- /**
227
- * Get the ID of the currently active panel
228
- * @returns Panel ID or null if no panel is active
229
- */
230
- getActive() {
231
- return this.pendingPanel?.id || null;
232
- }
233
- /**
234
- * Open a closable panelset
235
- * @param options - Configuration options
236
- */
237
- open(t) {
238
- const {
239
- event: i,
240
- transition: e = !0,
241
- autoFocus: l
242
- } = t || {};
243
- if (!this.config.closable) {
244
- this._log("Cannot open: closable is false");
245
- return;
246
- }
247
- const o = this.element.classList.contains("is-closed"), s = this.element.classList.contains("is-closing");
248
- if (!o && !s) return;
249
- const n = i?.target instanceof HTMLElement ? i.target.closest('button, a, [role="tab"]') ?? i.target : null;
250
- let a;
251
- if (n?.hasAttribute("data-auto-focus")) {
252
- const r = n.getAttribute("data-auto-focus");
253
- r === "true" ? a = !0 : r === "false" ? a = !1 : (r === "heading" || r === "first" || r === "input") && (a = r);
254
- } else l !== void 0 ? a = l : a = this.config.autoFocus;
255
- this._animateOpenClose(!0, e), a !== !1 && a !== void 0 && this.pendingPanel && (e && this.config.transitions ? this._waitForTransition(this.element).then(() => {
256
- this._handleAutoFocus(this.pendingPanel, a, i);
257
- }) : this._handleAutoFocus(this.pendingPanel, a, i));
258
- }
259
- /**
260
- * Close a closable panelset
261
- * @param options - Configuration options
262
- */
263
- close(t) {
264
- const {
265
- transition: i = !0
266
- } = t || {};
267
- if (!this.config.closable) {
268
- this._log("Cannot close: closable is false");
269
- return;
270
- }
271
- const e = this.element.classList.contains("is-closed"), l = this.element.classList.contains("is-opening");
272
- e && !l || this._animateOpenClose(!1, i);
273
- }
274
- /**
275
- * Toggle a closable panelset between open and closed
276
- * @param options - Configuration options
277
- */
278
- toggle(t) {
279
- const {
280
- event: i,
281
- transition: e = !0,
282
- autoFocus: l
283
- } = t || {}, o = this.element.classList.contains("is-closed"), s = this.element.classList.contains("is-closing");
284
- o || s ? this.open({ event: i, transition: e, autoFocus: l }) : this.close({ transition: e });
285
- }
286
- /**
287
- * Register a handler for async content loading
288
- * @param handler - Async content handler function
289
- * @param options - Handler options (once: whether to load only once)
290
- */
291
- onBeforeActivate(t, i = {}) {
292
- const e = i.once === !0;
293
- this.element.addEventListener("ps:beforeactivate", (l) => {
294
- const o = l, { targetPanel: s, signal: n } = o.detail;
295
- if (e && s.dataset.loaded === "true") {
296
- this.config.debug && this._log(`Skipping ${s.id} (already loaded)`);
297
- return;
298
- }
299
- const a = t(s, n);
300
- a && typeof a.then == "function" && (o.detail.promise = a.then(() => {
301
- e && (s.dataset.loaded = "true");
302
- }).catch((r) => {
303
- throw r.name === "AbortError" ? (this.config.debug && this._log(`Load aborted: ${s.id}`), r) : (this._log(`Load failed: ${r.message}`), r);
304
- }));
305
- });
306
- }
307
- /* --- Main logic --- */
308
- /**
309
- * Show a panel by ID
310
- * @param panelId - ID of the panel to show
311
- * @param options - Configuration options for this activation
312
- */
313
- async show(t, i) {
314
- const {
315
- event: e,
316
- transition: l = !0,
317
- autoFocus: o
318
- } = i || {}, s = e?.target instanceof HTMLElement ? e.target.closest('button, a, [role="tab"]') ?? e.target : null;
319
- let n;
320
- if (s?.hasAttribute("data-auto-focus")) {
321
- const c = s.getAttribute("data-auto-focus");
322
- c === "true" ? n = !0 : c === "false" ? n = !1 : (c === "heading" || c === "first" || c === "input") && (n = c);
323
- } else o !== void 0 ? n = o : n = this.config.autoFocus;
324
- if (s?.getAttribute("role") === "tab") {
325
- const c = s.closest('[role="tablist"]');
326
- c && (c.querySelectorAll('[role="tab"]').forEach((f) => {
327
- f.setAttribute("aria-selected", "false");
328
- }), s.setAttribute("aria-selected", "true"));
329
- }
330
- const a = this.panels.find((c) => c.id === t);
331
- if (!a) {
332
- this._log(`Panel not found: ${t}`);
333
- return;
334
- }
335
- if (a === this.pendingPanel) return;
336
- const r = this.pendingPanel, _ = r?.id;
337
- this.pendingPanel = a, this.element.classList.remove("is-loading"), r && r !== this.activePanel && r !== a && (r.classList.remove("incoming"), r.hidden || r.classList.remove("active"));
338
- const C = this._isLoadingAsync;
339
- this._currentAbortController && (this._currentAbortController.abort(), C && _ && _ !== t && this._dispatch("ps:activationaborted", {
340
- panelId: _,
341
- trigger: s
342
- }));
343
- const p = new AbortController();
344
- this._currentAbortController = p, this._isLoadingAsync = !1, this._log(`${r?.id || "none"} → ${t}`);
345
- const P = {
346
- panelId: t,
347
- targetPanel: a,
348
- outgoingPanel: r,
349
- signal: p.signal,
350
- promise: null
351
- }, E = new CustomEvent("ps:beforeactivate", {
352
- detail: P,
353
- bubbles: !0,
354
- cancelable: !1
355
- });
356
- this.element.dispatchEvent(E);
357
- const A = P.promise;
358
- if (A) {
359
- this._isLoadingAsync = !0, this._log("Waiting for content...");
360
- let c, d = !1;
361
- if (this.config.loadingDelay > 0 ? c = setTimeout(() => {
362
- this.element.classList.add("is-loading"), d = !0;
363
- }, this.config.loadingDelay) : (this.element.classList.add("is-loading"), d = !0), !(this.activePanel && this.activePanel !== a)) {
364
- const g = l !== !1 && this.config.transitions !== !1;
365
- let m = g;
366
- if (typeof this.config.transitions == "object" && (m = g && this.config.transitions.height !== !1), m) {
367
- const w = this.element.offsetHeight;
368
- this.element.style.height = `${w}px`, requestAnimationFrame(() => {
369
- this.element.style.height = `${this.config.emptyPanelHeight}px`;
370
- });
371
- }
372
- }
373
- try {
374
- if (await A, c && clearTimeout(c), p.signal.aborted) {
375
- this._log(`Aborted during load: ${t}`), d && this.element.classList.remove("is-loading");
376
- return;
377
- }
378
- this._log("Content loaded"), a.dataset.loaded === "true" && this._updateHighestPanel();
379
- } catch (g) {
380
- c && clearTimeout(c);
381
- const m = g;
382
- this._log(`Load failed: ${m.message}`), d && this.element.classList.remove("is-loading"), m.name !== "AbortError" && console.error("Panel load error:", g);
383
- return;
384
- }
385
- d && this.element.classList.remove("is-loading");
386
- }
387
- if (p.signal.aborted) {
388
- this._log(`Aborted: ${t}`);
389
- return;
390
- }
391
- this._dispatch("ps:activationstart", {
392
- panelId: t,
393
- trigger: s
394
- });
395
- const b = l !== !1 && this.config.transitions !== !1;
396
- let v = b, y = b;
397
- typeof this.config.transitions == "object" && (v = b && this.config.transitions.panels !== !1, y = b && this.config.transitions.height !== !1), this.panels.forEach((c) => c.classList.toggle("fade", v));
398
- const L = this.element.offsetHeight;
399
- y && (this.element.style.height = `${L}px`);
400
- const u = this.activePanel;
401
- a.hidden = !1, a.classList.add("incoming"), v && this.element.classList.add("is-transitioning"), u && u !== a && (u.classList.remove("active", "incoming"), u.hidden = !1), requestAnimationFrame(() => {
402
- a.classList.add("active"), u && u !== a && u.classList.remove("incoming");
403
- const c = this._measureHeight(a), d = L !== c;
404
- y && (this.element.style.height = `${c}px`);
405
- const f = [];
406
- v && f.push(this._waitForTransition(a)), y && d && f.push(this._waitForTransition(this.element)), f.length || f.push(Promise.resolve()), Promise.all(f).then(() => {
407
- if (this.pendingPanel !== a) {
408
- this._log(`Interrupted: ${t}`);
409
- return;
410
- }
411
- this._cleanupPanels(a), this._log(`✓ ${t}`), n !== !1 && n !== void 0 && this._handleAutoFocus(a, n, e), this._dispatch("ps:activationcomplete", {
412
- panelId: t,
413
- trigger: s
414
- });
415
- });
416
- });
417
- }
418
- };
419
- h.defaults = {
420
- transitions: !0,
421
- closable: !1,
422
- emptyPanelHeight: 200,
423
- loadingDelay: 300,
424
- autoFocus: !1,
425
- debug: !1
426
- };
427
- let $ = h;
428
- export {
429
- $ as PanelSet,
430
- $ as default
431
- };
432
- //# sourceMappingURL=panelset.js.map
1
+ !function(){var t=class{constructor(){this.t=new AbortController}get signal(){return this.t.signal}start(){return this.t.abort(),this.t=new AbortController,this.t.signal}static waitForTransition(t,i){return new Promise(s=>{const e=getComputedStyle(t),n=(parseFloat(e.transitionDuration)||0)+(parseFloat(e.transitionDelay)||0);if(0===n)return s();let o=!1;const a=()=>{o||(o=!0,t.removeEventListener("transitionend",h),s())},h=s=>{s.target===t&&(i&&s.propertyName!==i||a())};t.addEventListener("transitionend",h),setTimeout(a,1e3*(n+.05))})}};function i(t,i,s){if(!i)return;if("input"!==i&&s&&!(s.type.startsWith("key")||s instanceof MouseEvent&&0===s.detail))return;const e=t=>{setTimeout(()=>t.focus(),100)};if(!0===i)t.hasAttribute("tabindex")||t.setAttribute("tabindex","-1"),e(t);else if("heading"===i){const i=t.querySelector("h1, h2, h3, h4, h5, h6");i&&(i.hasAttribute("tabindex")||i.setAttribute("tabindex","-1"),e(i))}else if("first"===i){const i=t.querySelector('a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])');i&&e(i)}else if("input"===i){const i=t.querySelector("input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled])");i&&e(i)}else"function"==typeof i&&setTimeout(()=>i(t),100)}var s=()=>{const t=new URLSearchParams(location.search).get("panel");return t?t.split(",").filter(Boolean):[]},e=t=>{const i=new URL(location.href);if(i.searchParams.delete("panel"),t.length){const s=i.search?"&":"?";history.replaceState(null,"",`${i}${s}panel=${t.join(",")}`)}else history.replaceState(null,"",i)},n=t=>`${location.pathname}::${t}`,o=t=>localStorage.getItem(n(t)),a=(t,i)=>localStorage.setItem(n(t),i);function h(t,i){switch(i){case"string":return t;case"boolean":return"false"!==t;case"number":return parseInt(t,10);case"json":try{return JSON.parse(t)}catch{return"false"!==t}}}function l(t,i){const s={};for(const[e,n]of Object.entries(i)){const[i,o]=n,a=t[i];void 0!==a&&(s[e]=h(a,o))}return s}function r(t,i){const s={};for(const[e,n]of Object.entries(i)){const[i,o]=n,a=i.replace(/([A-Z])/g,"-$1").toLowerCase(),l=t.getAttribute(a);null!==l&&(s[e]=h(l,o))}return s}function c(t,i,s,e){s&&console.log(`[${t}] "${i.id||"no id"}" -`,e)}function p(t,i,s){const e=i.split(/\s+/).filter(Boolean);if(!e.length)return;const n=(t.getAttribute("aria-describedby")||"").split(/\s+/).filter(Boolean);let o=!1;for(const a of e){const t=n.includes(a);s&&!t?(n.push(a),o=!0):!s&&t&&(n.splice(n.indexOf(a),1),o=!0)}o&&(n.length?t.setAttribute("aria-describedby",n.join(" ")):t.removeAttribute("aria-describedby"))}var d,u=!1;function g(t){t&&!u&&(u=!0,console.log("[Panel/PanelSet] Browser supports 'interpolate-size', which will be used for opening and closing."))}function f(t){t.waitUntil=i=>{t.promise=t.promise?Promise.all([t.promise,i]):i}}function b(t,i,s,e,n={}){const o=!0===n.once;t.addEventListener(i,t=>{const i=t,n=s(i.detail),{signal:a}=i.detail;if(o&&"true"===n.dataset.loaded)return;const h=e(n,a);h&&"function"==typeof h.then&&i.detail.waitUntil(h.then(()=>{o&&(n.dataset.loaded="true")}))})}var m=class n{static init(t={},i={}){let s,e;"string"==typeof t?(s=t,e=i):(e=t,s=e.selector||"[data-panelset]");const o=document.querySelectorAll(s),a=[];return o.forEach(t=>{try{n.i(t)}catch(s){return void console.error(s.message)}if(t.panelSet)return void a.push(t.panelSet);const i=new n(t,e);a.push(i)}),a}constructor(i,h={}){let r;if(this.hasAsyncContent=!1,this.o=new t,this.h=new t,this.l=!1,this.p=!1,this.u=null,this.m=null,this.v=null,this.$=new AbortController,this.S=()=>{const t=s();return this.panels.find(i=>i.id&&t.includes(i.id))?.id??null},this.A=t=>{if(this.config.persist&&this.element.id&&a(`ps:${this.element.id}`,t),this.config.deepLink)this._(t);else{const t=new Set(this.panels.map(t=>t.id).filter(Boolean)),i=s();i.some(i=>t.has(i))&&e(i.filter(i=>!t.has(i)))}},this._=t=>{const i=new Set(this.panels.map(t=>t.id).filter(Boolean));e([...s().filter(t=>!i.has(t)),t].filter(Boolean))},this.P=()=>{const t=this.S();if(t)return t;if(this.config.persist){const{id:t}=this.element;if(!t)return null;const i=o(`ps:${t}`);return i&&this.panels.some(t=>t.id===i)?i:null}return null},this.F=t=>{const{atStart:i,atEnd:s}=t.detail;this.k(i,s)},"string"==typeof i){if(r=document.querySelector(i),!r)throw new Error(`PanelSet: No element found for selector "${i}"`)}else r=i;if(this.element=r,n.i(r),r.panelSet)return console.warn("PanelSet: already initialized"),r.panelSet;r.panelSet=this,n.C();const c=l(r.dataset,n.attrs);if(this.config={...n.defaults,...h,...c},this.panels=this.T(),0===this.panels.length)return this.panelWrapper=this.element.querySelector(":scope > .panel-wrapper")||this.L(),this.O("Initialized empty (0 panels) — ready for addPanel()"),this.H("ps:ready",{container:this.element,instance:this}),this.I(),void this.q();const p=this.P();this.activePanel=(p?this.panels.find(t=>t.id===p):null)??this.panels.find(t=>t.classList.contains("active"))??this.panels[0],this.panelWrapper=this.element.querySelector(":scope > .panel-wrapper")||this.L(),this.pendingPanel=this.activePanel,"start"!==this.config.align&&(this.element.dataset.panelsetAlign=this.config.align),this.config.closable&&!this.element.classList.contains("is-open")&&this.element.setAttribute("inert",""),n.D&&g(this.config.debug),this.O(`Initialized (${this.panels.length} panels)`),this.H("ps:ready",{container:this.element,instance:this}),this.M(),this.I(),this.q()}q(){const t=this.element.closest("[data-panelset-trackheight]");if(!t||"undefined"==typeof ResizeObserver)return;let i=t.clientWidth,s=0;this.v=new ResizeObserver(()=>{const e=t.clientWidth;e!==i&&(i=e,cancelAnimationFrame(s),s=requestAnimationFrame(()=>this.B()))}),this.v.observe(t)}O(t){c("PanelSet",this.element,this.config.debug,t)}get R(){return this.config.closable&&!this.element.classList.contains("is-open")&&!this.element.classList.contains("is-opening")}static i(t){if(!t.hasAttribute("data-panelset")&&!t.tagName.includes("-"))throw new Error("PanelSet: element must have [data-panelset] or be a custom element")}L(){const t=document.createElement("div");return t.className="panel-wrapper",this.panels.forEach(i=>t.appendChild(i)),this.element.appendChild(t),t}T(){return Array.from(this.element.querySelectorAll('[role="tabpanel"]')).filter(t=>t.closest("[data-panelset], ps-panelset, [data-panel], ps-panel")===this.element)}M(){this.panels.forEach(t=>{t.classList.remove("fade","incoming","outgoing","levelup","leveldown"),t!==this.activePanel?(t.hidden=!0,t.classList.remove("active")):(t.hidden=!1,t.classList.add("active"))}),this.element.style.height="",this.B(),this.config.manageTriggers&&this.V(this.activePanel),this.config.manageLabels&&this.N()}H(t,i){this.element.dispatchEvent(new CustomEvent(t,{detail:i,bubbles:!0,cancelable:!1}))}j(t){if(!t)return 0;const i=getComputedStyle(t);return(parseFloat(i.paddingTop)||0)+(parseFloat(i.paddingBottom)||0)+(parseFloat(i.borderTopWidth)||0)+(parseFloat(i.borderBottomWidth)||0)}U(t){let i=t.offsetHeight;return i+=this.j(this.panelWrapper),i+=this.j(this.element),i}B(){if(this.element.hasAttribute("data-panelset-trackheight"))return void console.warn("PanelSet: data-panelset-trackheight on parent only");const t=this.element.closest("[data-panelset-trackheight]");if(!t)return;this.panels.forEach(t=>{t.hidden=!0,t.classList.remove("active")});let i=0;this.panels.forEach(t=>{t.hidden=!1,t.classList.add("active"),t.style.visibility="hidden";const s=this.element.offsetHeight;s>i&&(i=s),t.hidden=!0,t.classList.remove("active"),t.style.visibility=""}),this.activePanel&&(this.activePanel.hidden=!1,this.activePanel.classList.add("active")),t.style.setProperty("--ps-max-height",`${i}px`),this.O(`Max container height: ${i}px`)}V(t){this.panels.forEach(i=>{i.id&&document.querySelectorAll(`[aria-controls="${i.id}"]`).forEach(s=>{s.setAttribute("aria-selected",String(i===t))})})}N(){this.panels.forEach(t=>{if(!t.id)return;if(t.hasAttribute("aria-labelledby")||t.hasAttribute("aria-label"))return;const i=Array.from(document.querySelectorAll(`[aria-controls="${t.id}"]`));if(0===i.length)return;const s=i.filter(t=>"tab"===t.getAttribute("role")),e=1===s.length?s[0]:1===i.length?i[0]:null;e&&(e.id||(e.id=this.W(`${t.id}-tab`)),t.setAttribute("aria-labelledby",e.id))})}W(t){let i=t,s=2;for(;document.getElementById(i);)i=`${t}-${s++}`;return i}G(t){this.panels.forEach(i=>{i.id&&document.querySelectorAll(`[aria-controls="${i.id}"]`).forEach(i=>{i.classList.toggle("is-activating",t)})})}J(t){this.panels.forEach(i=>{i.classList.remove("fade","incoming","outgoing","levelup","leveldown"),i!==t?(i.classList.remove("active"),i.hidden=!0):(i.classList.add("active"),i.hidden=!1,i.removeAttribute("inert"))}),this.element.style.height="",this.element.classList.remove("is-transitioning"),this.activePanel=t,this.config.manageTriggers&&this.V(t)}K(t,i){if(this.config.manageTriggers){const i=t?.getAttribute("data-auto-focus");if(null!=i){if("true"===i)return!0;if("false"===i)return!1;if("heading"===i||"first"===i||"input"===i)return i}}return void 0!==i?i:this.config.autoFocus}Z(t,s,e){i(t,s,e)}X(i,s,e){const o="is-"+(i?"closing":"opening"),a="is-"+(i?"opening":"closing");this.O(i?"Opening":"Closing");const h=this.h.start(),l=this.element.classList.contains(o)?this.element.offsetHeight:null,r=i?null:this.element.offsetHeight;this.element.classList.remove(o),i||this.element.classList.remove("is-open"),i&&this.element.removeAttribute("inert");const c=()=>{this.element.setAttribute("inert","");const t=e instanceof PointerEvent&&""!==e.pointerType;this.config.returnFocus&&this.m&&!t&&this.m.focus()};if(s&&this.config.transitions)if(this.element.classList.add(a),n.D)i?(this.element.style.height=null!==l?`${l}px`:"0px",requestAnimationFrame(()=>{this.element.style.height="",t.waitForTransition(this.element,"height").then(()=>{h.aborted||(this.panelWrapper?t.waitForTransition(this.panelWrapper):Promise.resolve()).then(()=>{h.aborted||(this.element.classList.remove(a),this.element.classList.add("is-open"),this.element.offsetHeight)})})})):(this.element.style.height=null!==l?`${l}px`:`${r}px`,requestAnimationFrame(()=>{this.element.style.height="",t.waitForTransition(this.element,"height").then(()=>{h.aborted||(this.element.classList.remove(a),this.element.offsetHeight,c())})}));else{const s=i?this.U(this.pendingPanel):0,e=null!==l?l:i?this.element.offsetHeight:r??0;this.element.style.height=`${e}px`,getComputedStyle(this.element).height,requestAnimationFrame(()=>{this.element.style.height=`${s}px`,t.waitForTransition(this.element).then(()=>{h.aborted||(this.element.style.height="",this.element.classList.remove(a),i&&this.element.classList.add("is-open"),this.element.offsetHeight,i||c())})})}else i?this.element.classList.add("is-open"):(this.element.classList.remove("is-open"),c()),this.element.style.height=""}getActive(){return this.pendingPanel?.id||null}refresh(){const t=this.activePanel;this.panels=this.T(),0!==this.panels.length&&(this.activePanel=t&&this.panels.includes(t)?t:this.panels.find(t=>t.classList.contains("active"))??this.panels[0],this.pendingPanel=this.activePanel,this.panelWrapper=this.element.querySelector(":scope > .panel-wrapper")||this.panelWrapper,this.M(),this.Y(),this.O(`Refreshed (${this.panels.length} panels)`))}addPanel(t,i){t.hasAttribute("role")||t.setAttribute("role","tabpanel");let s=null;return i?.before?s=this.panels.find(t=>t.id===i.before)??null:i?.after?s=this.panels.find(t=>t.id===i.after)?.nextElementSibling??null:"number"==typeof i?.index&&(s=this.panels[i.index]??null),(this.panelWrapper||this.L()).insertBefore(t,s),this.refresh(),t}removePanel(t){const i=this.panels.find(i=>i.id===t);i&&(i.remove(),this.refresh())}destroy(){this.o.start(),this.h.start(),this.v?.disconnect(),this.v=null,this.$.abort(),delete this.element.panelSet,this.O("Destroyed")}tt(t){const i=this.panels.length,s=t?this.panels.indexOf(t):-1;return{index:s,total:i,atStart:s<=0,atEnd:s===i-1}}static C(){n.it||(n.it=!0,document.addEventListener("click",n.st))}static et(t,i){const s=t.getAttribute(`data-ps-${i}`);return s?document.querySelector(s):t.closest("[data-panelset], ps-panelset")}I(){const{signal:t}=this.$;this.element.addEventListener("ps:activationstart",this.F,{signal:t}),this.element.addEventListener("ps:activationcomplete",this.F,{signal:t}),this.Y()}Y(){const{atStart:t,atEnd:i}=this.tt(this.pendingPanel);this.k(t,i)}k(t,i){if(this.config.loop)return;const s=this.nt("prev"),e=this.nt("next");if("native"!==this.config.disabledMode)return s.forEach(i=>this.ot(i,t)),void e.forEach(t=>this.ot(t,i));t||s.forEach(t=>this.ot(t,!1)),i||e.forEach(t=>this.ot(t,!1)),t&&this.ht(s,i?[]:e),i&&this.ht(e,t?[]:s)}ot(t,i){"native"===this.config.disabledMode?i?t.setAttribute("disabled",""):t.removeAttribute("disabled"):t.setAttribute("aria-disabled",String(i));const s=t.getAttribute("data-ps-disabled-hint");s&&p(t,s,i)}ht(t,i){t.forEach(t=>{t.hasAttribute("disabled")||document.activeElement!==t||(i.find(t=>!t.hasAttribute("disabled"))??this.lt())?.focus(),this.ot(t,!0)})}lt(){const t=this.pendingPanel??this.activePanel;return t?(t.hasAttribute("tabindex")||t.setAttribute("tabindex","-1"),t):null}nt(t){return Array.from(document.querySelectorAll(`[data-ps-${t}]`)).filter(i=>n.et(i,t)===this.element)}next(t){this.rt(1,t)}prev(t){this.rt(-1,t)}rt(t,i){const s=this.panels.length;if(0===s)return;const e=this.panels.indexOf(this.pendingPanel);let n=(-1===e?0:e)+t,o=!1;if(n<0||n>=s){if(!this.config.loop)return;n=(n+s)%s,o=!0}const a=this.panels[n];a&&a!==this.pendingPanel&&this.show(a.id,o?{...i,direction:t>0?"forward":"backward"}:i)}open(i){const{event:s,transition:e=!0,autoFocus:n}=i||{};if(!this.config.closable)return void this.O("Not closable");const o=this.R,a=this.element.classList.contains("is-closing"),h=this.element.classList.contains("is-loading");if(!o&&!a)return;if(this.element.classList.contains("is-transitioning")&&!h)return;const l=s?.target instanceof HTMLElement?s.target.closest('button, a, [role="tab"]')??s.target:null,r=this.K(l,n);l&&(this.m=l),this.X(!0,e),!1!==r&&void 0!==r&&this.pendingPanel&&(e&&this.config.transitions?t.waitForTransition(this.element).then(()=>{this.Z(this.pendingPanel,r,s)}):this.Z(this.pendingPanel,r,s))}close(t){const{transition:i=!0,event:s}=t||{};if(!this.config.closable)return void this.O("Not closable");const e=this.R,n=this.element.classList.contains("is-closing"),o=this.element.classList.contains("is-opening"),a=this.element.classList.contains("is-loading");(!e&&!n||o)&&(this.element.classList.contains("is-transitioning")&&!a||this.X(!1,i,s))}toggle(t){const{event:i,transition:s=!0,autoFocus:e}=t||{},n=this.R,o=this.element.classList.contains("is-closing");n||o?this.open({event:i,transition:s,autoFocus:e}):this.close({transition:s,event:i})}onBeforeOpen(t,i={}){this.hasAsyncContent=!0,b(this.element,"ps:beforeopen",t=>t.targetPanel,t,i)}async show(i,s){if(!1===this.config.interruptible&&this.p)return;const{event:e,transition:n=!0,autoFocus:o,direction:a}=s||{},h=e?.target instanceof HTMLElement?e.target.closest('button, a, [role="tab"]')??e.target:null,l=this.K(h,o),r=this.panels.find(t=>t.id===i);if(!r)return void this.O(`Panel not found: ${i}`);const c=new CustomEvent("ps:beforeactivate",{detail:{panelId:i,targetPanel:r,outgoingPanel:this.activePanel??null,trigger:h},bubbles:!0,cancelable:!0});if(!this.element.dispatchEvent(c))return void this.O(`Vetoed by ps:beforeactivate: ${i}`);const p=this.R,d=this.element.classList.contains("is-closing"),u=this.element.classList.contains("is-loading");if(r===this.pendingPanel)return void(p||d?this.open({event:e,transition:n,autoFocus:l}):this.config.closable&&this.config.closeOnTab&&this.close({transition:n,event:e}));if((this.element.classList.contains("is-opening")||d)&&!u)return;if(p)return this.pendingPanel=r,this.J(r),void this.open({event:e,transition:n,autoFocus:l});const g=this.p;this.p=!0,this.config.manageTriggers&&!1===this.config.interruptible&&this.G(!0);const b=this.pendingPanel,m=b?.id;this.pendingPanel=r;const v=g&&r===this.activePanel&&b!==r;this.config.manageTriggers&&this.V(r),this.A(i),this.element.classList.remove("is-loading"),!v&&b&&b!==this.activePanel&&b!==r&&(b.classList.remove("incoming","outgoing","levelup","leveldown"),b.hidden||b.classList.remove("active"));const w=this.l,y=this.o.signal.aborted,$=this.o.start();!y&&w&&m&&m!==i&&this.H("ps:activationaborted",{panelId:m,trigger:h}),this.l=!1,this.O(`${b?.id||"none"} > ${i}`);const S={panelId:i,targetPanel:r,outgoingPanel:b,signal:$,promise:null,waitUntil(){}};f(S);const x=new CustomEvent("ps:beforeopen",{detail:S,bubbles:!0,cancelable:!1});this.element.dispatchEvent(x);const A=S.promise;if(A){this.l=!0,this.O("Waiting for content..."),this.element.style.setProperty("--ps-loading-delay",`${this.config.loadingDelay}ms`),this.element.classList.add("is-loading");let s=null;const e=!1!==n&&!1!==this.config.transitions;let o=e;if("object"==typeof this.config.transitions&&(o=e&&!1!==this.config.transitions.height),o)if(p)this.element.classList.add("is-open","is-opening"),this.element.style.height="0px",requestAnimationFrame(()=>{this.element.style.height=`${this.config.loadingHeight}px`}),s=t.waitForTransition(this.element,"height");else{const i=this.element.offsetHeight,e=Math.max(i,this.config.loadingHeight);e>i&&(this.element.style.height=`${i}px`,requestAnimationFrame(()=>{this.element.style.height=`${e}px`}),s=t.waitForTransition(this.element,"height"))}try{if(await Promise.all([A,s].filter(Boolean)),$.aborted)return this.O(`Aborted during load: ${i}`),this.element.classList.remove("is-loading"),void this.element.style.removeProperty("--ps-loading-delay");this.O("Content loaded"),"true"===r.dataset.loaded&&this.B()}catch(T){const t=T;return this.O(`Load failed: ${t.message}`),this.element.classList.remove("is-loading"),this.element.style.removeProperty("--ps-loading-delay"),"AbortError"!==t.name&&console.error("Panel load error:",T),this.p=!1,void(this.config.manageTriggers&&!1===this.config.interruptible&&this.G(!1))}this.element.classList.remove("is-loading"),this.element.style.removeProperty("--ps-loading-delay"),this.element.classList.remove("is-opening")}if($.aborted)return void this.O(`Aborted: ${i}`);const _=v?b:this.activePanel;this.H("ps:activationstart",{panelId:i,trigger:h,outgoingPanel:_,...this.tt(r)});const P=!1!==n&&!1!==this.config.transitions;let F=P,k=P;"object"==typeof this.config.transitions&&(F=P&&!1!==this.config.transitions.panels,k=P&&!1!==this.config.transitions.height),this.panels.forEach(t=>t.classList.toggle("fade",F));let C=null;if(v)C="leveldown"===this.u?"levelup":"leveldown";else if(this.config.levels&&_&&_!==r)if(a)C="forward"===a?"levelup":"leveldown";else{const t=this.panels.indexOf(_),i=this.panels.indexOf(r);-1!==t&&-1!==i&&t!==i&&(C=i>t?"levelup":"leveldown")}this.u=C,this.panels.forEach(t=>t.classList.remove("outgoing","levelup","leveldown"));const E=this.element.offsetHeight;k&&(this.element.style.height=`${E}px`),r.hidden=!1,r.setAttribute("inert",""),r.classList.add("incoming"),C&&r.classList.add(C),F&&this.element.classList.add("is-transitioning"),_&&_!==r&&(_.classList.remove("active","incoming"),_.classList.add("outgoing"),C&&_.classList.add(C),_.hidden=!1,_.setAttribute("inert","")),requestAnimationFrame(()=>requestAnimationFrame(()=>{r.classList.add("active"),_&&_!==r&&_.classList.remove("incoming");const s=this.U(r),n=E!==s;k&&(this.element.style.height=`${s}px`);const o=[];F&&o.push(t.waitForTransition(r)),k&&n&&o.push(t.waitForTransition(this.element)),o.length||o.push(Promise.resolve()),Promise.all(o).then(()=>{$.aborted?this.O(`Interrupted: ${i}`):(this.J(r),this.O(`✓ ${i}`),!1!==l&&void 0!==l&&this.Z(r,l,e),this.p=!1,this.config.manageTriggers&&!1===this.config.interruptible&&this.G(!1),this.H("ps:activationcomplete",{panelId:i,trigger:h,outgoingPanel:_,...this.tt(r)}))})}))}};(d=m).defaults={align:"start",transitions:!0,levels:!1,loop:!1,closable:!1,closeOnTab:!1,disabledMode:"aria",loadingHeight:150,loadingDelay:320,returnFocus:!1,autoFocus:!1,persist:!1,deepLink:!1,interruptible:!0,manageTriggers:!0,manageLabels:!0,debug:!1},d.D="undefined"!=typeof CSS&&CSS.supports("interpolate-size: allow-keywords"),d.it=!1,d.attrs={align:["panelsetAlign","string"],transitions:["transitions","json"],levels:["psLevels","boolean"],loop:["psLoop","boolean"],closable:["closable","boolean"],closeOnTab:["closeOnTab","boolean"],disabledMode:["psDisabledMode","string"],loadingHeight:["loadingHeight","number"],loadingDelay:["loadingDelay","number"],autoFocus:["autoFocus","string"],returnFocus:["returnFocus","boolean"],persist:["panelPersist","boolean"],deepLink:["panelDeeplink","boolean"],interruptible:["interruptible","boolean"],manageTriggers:["manageTriggers","boolean"],manageLabels:["manageLabels","boolean"],debug:["debug","boolean"]},d.st=t=>{const i=t.target;if(!(i instanceof Element))return;const s=i.closest("[data-ps-next], [data-ps-prev], [data-ps-close]");if(!s||"true"===s.getAttribute("aria-disabled"))return;const e=s.hasAttribute("data-ps-next")?"next":s.hasAttribute("data-ps-prev")?"prev":"close",n=d.et(s,e),o=n?.panelSet;o?o[e]({event:t}):n&&c("PanelSet",n,null!=n.dataset.debug&&"false"!==n.dataset.debug,`data-ps-${e}: PanelSet is not initialised. Add a PanelSet.init().`)};var v,w,y=t=>{const i=t.parentElement;return i?.querySelector("[data-panel-body]")?Array.from(i.children).find(t=>t instanceof HTMLElement&&t.hasAttribute("data-panel-body"))??null:null},$=t=>{const i=Array.from(t.querySelectorAll("[data-panel-pin]"));i.length&&i.map(t=>({el:t,pin:t.dataset.panelPin,w:t.offsetWidth})).forEach(({el:t,pin:i,w:s})=>{t.style.boxSizing="border-box",t.style.width=`${s}px`,"end"===i&&(t.style.marginLeft=`calc(100% - ${s}px)`)})},S=t=>{t.querySelectorAll("[data-panel-pin]").forEach(t=>{t.style.boxSizing="",t.style.width="",t.style.marginLeft=""})},x=class n{static init(t="[data-panel]",i={}){let s,e;return"string"==typeof t?(s=t,e=i):(e=t,s="[data-panel]"),Array.from(document.querySelectorAll(s)).filter(t=>!t.panel).filter(t=>!t.dataset.panel||"data-panel"===t.dataset.panel).map(t=>new n(t,e))}constructor(i,h={}){this.m=null,this.ct=null,this.dt=new t,this.ut=new AbortController,this.p=!1,this.S=()=>{const{id:t}=this.element;return!!t&&s().includes(t)},this._=t=>{const{id:i}=this.element;if(!i)return;const n=s();e(t?[...new Set([...n,i])]:n.filter(t=>t!==i))},this.gt=()=>{const t=t=>this.element.hasAttribute(t)?"false"!==this.element.getAttribute(t):void 0,i=t("data-panel-persist"),s=t("data-panel-deeplink");let e,n,o=this.element.parentElement;for(;o&&!o.hasAttribute("data-panel");){if(o.hasAttribute("data-panel-group")){o.hasAttribute("data-panel-persist")&&(e="false"!==o.getAttribute("data-panel-persist")),o.hasAttribute("data-panel-deeplink")&&(n="false"!==o.getAttribute("data-panel-deeplink"));break}o=o.parentElement}return{persist:i??e??this.config.persist,deepLink:s??n??this.config.deepLink}},this.A=t=>{if(!this.element.id)return;const{persist:i,deepLink:s}=this.gt();i&&a(`panel:${this.element.id}`,t?"open":"closed"),s?this._(t):!t&&this.S()&&this._(!1)},this.ft=()=>{const{id:t}=this.element;if(!t)return!1;if(this.S())return!0;const{persist:i}=this.gt();return!(!i||"open"!==o(`panel:${t}`))},this.bt=()=>"horizontal"===this.config.axis?"width":"height";const r="string"==typeof i?document.querySelector(i):i;if(!r)throw new Error(`Panel: No element found for selector "${i}"`);if(this.element=r,r.panel=this,!r.querySelector(":scope > .panel-wrapper")){const t=document.createElement("div");t.className="panel-wrapper",t.append(...Array.from(r.childNodes)),r.appendChild(t)}const c=l(r.dataset,n.attrs);this.config={...n.defaults,...h,...c},"horizontal"===this.config.axis&&(r.dataset.panelAxis="horizontal"),"start"!==this.config.align&&(r.dataset.panelAlign=this.config.align),this.vt(),this.wt(),this.ft()||this.element.classList.contains("is-open")?(this.element.classList.add("is-open"),this.element.removeAttribute("inert"),this.yt(!0),this.element.classList.add("is-restored"),requestAnimationFrame(()=>requestAnimationFrame(()=>this.element.classList.remove("is-restored"))),this.H("panel:opened")):this.element.setAttribute("inert",""),n.D&&g(this.config.debug),this.O("Initialized")}O(t){c("Panel",this.element,this.config.debug,t)}vt(){const t=t=>t.hasAttribute("data-panel")||!!t.panel,i=t=>{this.element.id||(this.element.id="panel-"+ ++n.$t),t.setAttribute("aria-controls",this.element.id),t.hasAttribute("aria-expanded")||t.setAttribute("aria-expanded","false"),t.removeAttribute("data-panel-trigger")},s=t=>t.hasAttribute("data-panel-trigger")?t:/^H[1-6]$/.test(t.tagName)?t.querySelector(":scope > [data-panel-trigger]"):null;for(const e of["previousElementSibling","nextElementSibling"]){let n=this.element[e];for(;n&&!t(n);){const t=s(n);if(t){i(t);break}n=n[e]}}}wt(){const t=this.element.id;if(!t)return;const{signal:i}=this.ut;document.querySelectorAll(`[aria-controls="${t}"]`).forEach(t=>{t.addEventListener("click",i=>{this.m=t,this.toggle(i)},{signal:i})}),this.element.querySelectorAll("[data-panel-close]").forEach(t=>{t.closest("[data-panel]")===this.element&&t.addEventListener("click",()=>this.close(),{signal:i})}),this.config.closeOnResize&&window.addEventListener("resize",()=>{if(!this.isOpen||this.element.classList.contains("is-closing"))return;const t=y(this.element);t&&S(t),this.close()},{signal:i})}yt(t){const i=this.element.id;i&&document.querySelectorAll(`[aria-controls="${i}"]`).forEach(i=>i.setAttribute("aria-expanded",String(t)))}St(){this.ct&&(this.ct.style.removeProperty("--ps-tempclose-speed"),this.ct.style.removeProperty("--ps-tempclose-timing"),this.ct=null)}xt(){const t=this.element.closest("[data-panel-group]"),i=t?.hasAttribute("data-panel-close-siblings")??!1;if(!this.config.closeSiblings&&!i)return;const s=t?function(t){const{groupSelector:i,scopeSelector:s=i,itemSelector:e,filter:n}={groupSelector:"[data-panel-group]",itemSelector:"[data-panel]",filter:t=>!!t.panel?.isOpen},o=(t,i)=>{if(t.closest(s)!==i)return!1;const n=t.parentElement?.closest(e);return!n||!i.contains(n)},a=t.closest(i);return a&&o(t,a)?[...a.querySelectorAll(e)].filter(i=>i!==t&&o(i,a)&&(!n||n(i))):[]}(this.element):Array.from(this.element.parentElement?.children??[]).filter(t=>t instanceof HTMLElement&&t!==this.element&&!!t.panel?.isOpen);s.length&&t&&(t.style.setProperty("--ps-tempclose-speed","var(--ps-open-speed)"),t.style.setProperty("--ps-tempclose-timing","var(--ps-open-timing)"),this.ct=t),s.forEach(t=>{t.panel.m=null,t.panel.close()})}Z(t){this.config.autoFocus&&i(this.element,this.config.autoFocus,t)}H(t){"panel:opened"!==t&&"panel:closed"!==t||(this.p=!1);const i={trigger:this.m};this.element.dispatchEvent(new CustomEvent(t,{detail:i,bubbles:!0}))}get isOpen(){return this.element.classList.contains("is-open")||this.element.classList.contains("is-opening")}async open(t){if(this.isOpen&&!this.element.classList.contains("is-closing"))return;if(!1===this.config.interruptible&&this.p)return;this.p=!0;const i=this.dt.start(),s=this.bt(),e={signal:i,promise:null,waitUntil(){},trigger:this.m};f(e),this.element.dispatchEvent(new CustomEvent("panel:beforeopen",{detail:e,bubbles:!0})),e.promise?await this.At(i,s,e.promise,t):this._t(i,s,t)}async At(i,s,e,o){const a=await Promise.race([e.then(()=>!0),new Promise(t=>{const s=setTimeout(()=>t(!1),this.config.loadingDelay);i.addEventListener("abort",()=>clearTimeout(s))})]).catch(()=>!1);if(i.aborted)return;if(a)return void this._t(i,s,o);this.element.classList.remove("is-closing"),this.element.removeAttribute("inert"),this.element.classList.add("is-loading"),this.element.querySelector(":scope > .panel-wrapper")?.replaceChildren(),this.element.style.setProperty("--ps-loading-delay","0ms"),this.yt(!0),this.A(!0),this.H("panel:opening");const h=y(this.element);let l;h&&$(h),this.config.transitions?(this.element.classList.add("is-opening"),this.element.style[s]="0px",getComputedStyle(this.element)[s],requestAnimationFrame(()=>{this.element.style[s]=`${this.config.loadingHeight}px`}),l=t.waitForTransition(this.element,s)):this.element.style[s]=`${this.config.loadingHeight}px`;try{await Promise.all([e,l].filter(Boolean))}catch{}finally{this.element.classList.remove("is-loading"),this.element.style.removeProperty("--ps-loading-delay")}if(i.aborted)return;const r=this.element.getBoundingClientRect(),c="height"===s?r.height:r.width;if(this.element.style[s]="",this.config.transitions)if(n.D)t.waitForTransition(this.element,s).then(()=>{i.aborted||(this.element.classList.remove("is-opening"),this.element.classList.add("is-open"),this.H("panel:opened"),this.O("Opened"),this.Z(o))});else{this.element.style[s]="auto";const e=this.element.getBoundingClientRect(),n="height"===s?e.height:e.width;this.element.style[s]=`${c}px`,getComputedStyle(this.element)[s],requestAnimationFrame(()=>{this.element.style[s]=`${n}px`,t.waitForTransition(this.element,s).then(()=>{i.aborted||(this.element.style[s]="",this.element.classList.remove("is-opening"),this.element.classList.add("is-open"),this.H("panel:opened"),this.O("Opened"),this.Z(o))})})}else this.element.classList.remove("is-opening"),this.element.classList.add("is-open"),this.H("panel:opened"),this.O("Opened"),this.Z(o)}_t(i,s,e){const o=this.element.classList.contains("is-closing")?this.element.getBoundingClientRect()["height"===s?"height":"width"]:null;this.element.classList.remove("is-closing"),this.element.removeAttribute("inert");const a=y(this.element);if(this.config.transitions)if(n.D)this.element.classList.add("is-opening"),this.element.style[s]=null!==o?`${o}px`:"0px",a&&$(a),getComputedStyle(this.element)[s],this.yt(!0),this.A(!0),this.xt(),this.H("panel:opening"),requestAnimationFrame(()=>{this.element.style[s]="",t.waitForTransition(this.element,s).then(()=>{i.aborted||(this.element.classList.remove("is-opening"),this.element.classList.add("is-open"),this.H("panel:opened"),this.St(),this.O("Opened"),this.Z(e))})});else{this.yt(!0),this.A(!0),this.xt(),this.H("panel:opening"),this.element.style[s]="auto";const n=this.element.getBoundingClientRect(),h="height"===s?n.height:n.width;this.element.style[s]=null!==o?`${o}px`:"0px",this.element.classList.add("is-opening"),a&&$(a),getComputedStyle(this.element)[s],requestAnimationFrame(()=>{this.element.style[s]=`${h}px`,t.waitForTransition(this.element,s).then(()=>{i.aborted||(this.element.style[s]="",this.element.classList.remove("is-opening"),this.element.classList.add("is-open"),this.H("panel:opened"),this.St(),this.O("Opened"),this.Z(e))})})}else this.yt(!0),this.A(!0),this.xt(),this.H("panel:opening"),this.element.classList.add("is-open"),this.H("panel:opened"),this.St(),this.O("Opened"),this.Z(e)}onBeforeOpen(t,i={}){b(this.element,"panel:beforeopen",()=>this.element,t,i)}close(i){if(!this.isOpen)return;if(!1===this.config.interruptible&&this.p)return;this.p=!0,this.element.setAttribute("inert",""),this.yt(!1);const s=this.bt(),e=y(this.element),n=this.dt.start();this.H("panel:closing");const o=()=>{this.element.classList.remove("is-closing","is-open","is-opening"),this.element.style[s]="",e&&S(e),this.A(!1),this.H("panel:closed"),this.O("Closed");const t=i instanceof PointerEvent&&""!==i.pointerType;this.config.returnFocus&&this.m&&!t&&this.m.focus()};if(!this.config.transitions)return void o();const a=this.element.getBoundingClientRect(),h="height"===s?a.height:a.width;this.element.style[s]=`${h}px`,this.element.classList.remove("is-opening","is-open"),this.element.classList.add("is-closing"),getComputedStyle(this.element)[s],requestAnimationFrame(()=>{this.element.style[s]="0px",t.waitForTransition(this.element,s).then(()=>{n.aborted||o()})})}toggle(t){this.element.classList.contains("is-closing")?this.open(t):this.isOpen?this.close(t):(t?.target&&(this.m=t.target.closest("button, a")??t.target),this.open(t))}destroy(){this.dt.start(),this.ut.abort(),this.element.classList.remove("is-opening","is-closing","is-loading","is-open"),this.element.style[this.bt()]="",this.element.setAttribute("inert",""),this.yt(!1);const t=y(this.element);t&&S(t),delete this.element.panel,this.O("Destroyed")}};(v=x).defaults={axis:"vertical",align:"start",closeOnResize:!1,transitions:!0,autoFocus:!1,returnFocus:!0,closeSiblings:!1,loadingDelay:320,loadingHeight:150,interruptible:!0,persist:!1,deepLink:!1,debug:!1},v.attrs={axis:["panelAxis","string"],align:["panelAlign","string"],autoFocus:["panelAutoFocus","string"],closeOnResize:["panelCloseOnResize","boolean"],transitions:["panelTransitions","boolean"],returnFocus:["panelReturnFocus","boolean"],closeSiblings:["panelCloseSiblings","boolean"],loadingDelay:["panelLoadingDelay","number"],loadingHeight:["panelLoadingHeight","number"],interruptible:["panelInterruptible","boolean"],persist:["panelPersist","boolean"],deepLink:["panelDeeplink","boolean"],debug:["debug","boolean"]},v.D="undefined"!=typeof CSS&&CSS.supports("interpolate-size: allow-keywords"),v.$t=0;var A=class t{static init(i="[data-panelcontrol]",s={}){let e,n;return"string"==typeof i?(e=i,n=s):(n=i,e=n.selector||"[data-panelcontrol]"),Array.from(document.querySelectorAll(e)).filter(t=>!t.panelControl).map(i=>new t(i,n))}constructor(i,s={}){this.Pt=null,this.t=new AbortController,this.Ft=!1,this.kt=!1,this.Ct=()=>{const t=this.panelSet,i=this.panelSetElement;let s=!1;if(t)s=!(!t.config.closable||!t.config.closeOnTab);else if(i){const t=null!=i.dataset.closable||i.hasAttribute("closable"),e=null!=i.dataset.closeOnTab||i.hasAttribute("close-on-tab");s=t&&e}this.element.toggleAttribute("data-closeable",s)},this.Et=t=>"true"!==t.getAttribute("aria-disabled")&&!t.hidden,this.Tt=t=>{const i=this.Lt().find(i=>i.getAttribute("aria-controls")===t.detail?.panelId);i&&this.Ot(i)},this.zt=t=>{const i=this.Lt().filter(this.Et);if(!i.length)return;const s="vertical"===this.element.getAttribute("aria-orientation"),e=s?"ArrowDown":"ArrowRight",n=s?"ArrowUp":"ArrowLeft",o=i.indexOf(document.activeElement);let a;switch(t.key){case e:a=i[(o+1)%i.length];break;case n:a=i[(o-1+i.length)%i.length];break;case"Home":a=i[0];break;case"End":a=i[i.length-1];break;case"Enter":case" ":return void(o>=0&&(t.preventDefault(),this.Ht(i[o],t)));default:return}a&&(t.preventDefault(),this.Ot(a),a.focus(),this.It()&&this.Ht(a,t))};const e="string"==typeof i?document.querySelector(i):i;if(!e)throw new Error(`PanelControl: No element found for selector "${i}"`);if(this.element=e,e.panelControl)return console.warn("PanelControl: already initialized"),e.panelControl;e.panelControl=this;const n=l(e.dataset,t.attrs);this.config={...t.defaults,...s,...n},this.Ft="tablist"===e.getAttribute("role"),this.wt(),this.Ft&&this.qt();const o=this.panelSetElement;this.O(`Initialized (${o?"linked to a PanelSet":"no PanelSet found yet"}${this.Ft?", tablist keyboard nav":""})`)}get panelSetElement(){if(!this.Pt){const t=this.Dt();t&&(this.Pt=t,this.Mt(t))}return this.Pt}get panelSet(){return this.panelSetElement?.panelSet}show(t,i){this.panelSet?.show(t,i)}setTabState(t,i){const s="disabled"===i;this.element.querySelectorAll(`[aria-controls="${t}"]`).forEach(t=>{t.setAttribute("aria-disabled",String(s));const i=t.getAttribute("data-pc-disabled-hint");i&&p(t,i,s),this.Ft&&s&&(t.tabIndex=-1)}),this.Ft&&s&&this.Bt()}O(t){c("PanelControl",this.element,this.config.debug,t)}Dt(){const t=this.element.getAttribute("data-panelcontrol");if(t)return document.querySelector(t);const i=this.element.querySelector("[aria-controls]")?.getAttribute("aria-controls");return i?document.getElementById(i)?.closest("[data-panelset], ps-panelset")??null:null}Mt(t){const{signal:i}=this.t;this.Ft&&!this.kt&&(t.addEventListener("ps:activationcomplete",this.Tt,{signal:i}),this.kt=!0),this.Ct(),t.panelSet||t.addEventListener("ps:ready",this.Ct,{once:!0,signal:i})}wt(){const{signal:t}=this.t;this.element.querySelectorAll("[aria-controls]").forEach(i=>{i.addEventListener("click",t=>{this.Ht(i,t)},{signal:t})})}Ht(t,i){if("true"===t.getAttribute("aria-disabled"))return;const s=t.getAttribute("aria-controls");s&&(this.panelSet?(this.panelSet.show(s,{event:i}),this.Ft&&this.Ot(t)):this.O(`Can’t activate '${s}': its PanelSet is not initialised. Add a PanelSet.init().`))}Lt(){return Array.from(this.element.querySelectorAll('[role="tab"]'))}Ot(t){this.Lt().forEach(i=>{i.tabIndex=i===t?0:-1})}Bt(){const t=this.Lt();if(t.some(t=>0===t.tabIndex&&this.Et(t)))return;const i=t.find(t=>"true"===t.getAttribute("aria-selected")&&this.Et(t))??t.find(this.Et);i&&this.Ot(i)}qt(){const t=this.Lt();if(!t.length)return;const i=t.find(t=>"true"===t.getAttribute("aria-selected"))??t[0];this.Ot(i),this.element.addEventListener("keydown",this.zt,{signal:this.t.signal})}It(){return"auto"===this.config.activation&&!this.Rt()&&!this.panelSet?.hasAsyncContent}Rt(){const t=this.panelSet;if(t&&!1!==t.config.autoFocus)return!0;const i=this.panelSetElement,s=i&&(i.dataset.autoFocus??i.getAttribute("auto-focus"));return null!=s&&"false"!==s||this.Lt().some(t=>{const i=t.getAttribute("data-auto-focus");return null!=i&&"false"!==i})}destroy(){this.t.abort(),delete this.element.panelControl,this.O("Destroyed")}};(w=A).defaults={activation:"manual",debug:!1},w.attrs={activation:["activation","string"],debug:["debug","boolean"]};var _=class extends HTMLElement{connectedCallback(){if(this.panel)return;const t=r(this,x.attrs);new x(this,t)}disconnectedCallback(){}},P=class extends HTMLElement{connectedCallback(){if(this.panelSet)return;const t=r(this,m.attrs);new m(this,t)}disconnectedCallback(){}},F=class extends HTMLElement{connectedCallback(){if(this.panelControl)return;const t=r(this,A.attrs);new A(this,t)}disconnectedCallback(){}};window.PanelSet=m,window.Panel=x,window.PanelControl=A,function(t="ps"){const i=(t,i)=>{customElements.get(t)||customElements.define(t,i)};i(`${t}-panel`,_),i(`${t}-panelset`,P),i(`${t}-panelcontrol`,F)}()}();
@@ -0,0 +1,6 @@
1
+ import { t as e } from "./panelset-core.js";
2
+ //#region src/js/register.ts
3
+ e();
4
+ //#endregion
5
+
6
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","names":[],"sources":["../src/js/register.ts"],"sourcesContent":["import { register } from './index.js';\nregister();\n"],"mappings":";;AACA,EAAS"}
@@ -0,0 +1,18 @@
1
+ type AttrType = 'string' | 'boolean' | 'number' | 'json';
2
+ /** Maps each config key to its [dataset key, value type]. */
3
+ export type AttrMap<T> = {
4
+ [K in keyof T]?: [datasetKey: string, type: AttrType];
5
+ };
6
+ /**
7
+ * Parse data attributes from a DOMStringMap into a partial config object.
8
+ * Each entry maps a config key to a [datasetKey, type] pair.
9
+ */
10
+ export declare function parseDataAttrs<T>(dataset: DOMStringMap, attrMap: AttrMap<T>): Partial<T>;
11
+ /**
12
+ * Parse plain element attributes into a partial config object.
13
+ * Uses the same AttrMap as parseDataAttrs but reads from element.getAttribute()
14
+ * instead of dataset. The datasetKey is converted from camelCase to kebab-case
15
+ * to form the attribute name (e.g. "panelAxis" > "panel-axis").
16
+ */
17
+ export declare function parseAttrs<T>(element: Element, attrMap: AttrMap<T>): Partial<T>;
18
+ export {};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Animation engine for dimension transitions. One instance per element.
3
+ * start() aborts the previous cycle and returns a fresh AbortSignal.
4
+ * Pass it to fetch() and cancelling the animation cancels the request too.
5
+ */
6
+ export declare class Core {
7
+ private _controller;
8
+ get signal(): AbortSignal;
9
+ start(): AbortSignal;
10
+ static waitForTransition(el: HTMLElement, propertyName?: string): Promise<void>;
11
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The autoFocus mode. Pass to autoFocus() after opening a panel.
3
+ * - true : focus the panel element itself
4
+ * - 'heading' : focus the first heading (h1–h6)
5
+ * - 'first' : focus the first focusable element
6
+ * - 'input' : focus the first form field (bypasses keyboard-only check)
7
+ * - function : custom handler, called with the panel element
8
+ */
9
+ export type AutoFocusMode = boolean | 'heading' | 'first' | 'input' | ((el: HTMLElement) => void);
10
+ /**
11
+ * Move focus into a panel after it opens.
12
+ *
13
+ * Skips focus when the triggering event is a mouse/touch click (not
14
+ * keyboard), except for 'input' mode which always focuses.
15
+ */
16
+ export declare function autoFocus(el: HTMLElement, mode: AutoFocusMode, event?: Event): void;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * URL param + localStorage.
3
+ */
4
+ export declare const readPanelParam: () => string[];
5
+ export declare const writePanelParam: (ids: string[]) => void;
6
+ export declare const readStored: (key: string) => string | null;
7
+ export declare const writeStored: (key: string, value: string) => void;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Pinning elements (for 'push' panels)
3
+ */
4
+ export declare const findBody: (element: HTMLElement) => HTMLElement | null;
5
+ export declare const lockBody: (body: HTMLElement) => void;
6
+ export declare const unlockBody: (body: HTMLElement) => void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shared debug logger. Both Panel and PanelSet use this pattern.
3
+ */
4
+ export declare function log(prefix: string, element: HTMLElement, debug: boolean, message: string): void;
5
+ /**
6
+ * Add or remove id token(s) on an element's aria-describedby, leaving any other
7
+ * tokens (author-set descriptions) intact. `ids` may be a single id or a
8
+ * space-separated list. Used to attach a "why is this disabled" hint only while
9
+ * a control is disabled, so it is not announced when the control is enabled.
10
+ */
11
+ export declare function setDescribedBy(el: HTMLElement, ids: string, present: boolean): void;
12
+ /**
13
+ * Logs browser interpolate-size support once across all Panel and PanelSet instances.
14
+ * No-ops if debug is false or the message has already been logged.
15
+ */
16
+ export declare function logInterpolateSizeOnce(debug: boolean): void;
17
+ /** A before-open event detail that carries an awaitable promise for async content. */
18
+ export interface Awaitable {
19
+ /** Underlying mechanism the open awaits; prefer waitUntil(). */
20
+ promise: Promise<unknown> | null;
21
+ /** Delay the open until p resolves. Safe to call more than once (the open awaits
22
+ * all of them), and safe to destructure (it closes over the detail, not `this`). */
23
+ waitUntil(p: Promise<unknown>): void;
24
+ }
25
+ /**
26
+ * Wire detail.waitUntil() so it sets detail.promise, combining via Promise.all when
27
+ * called more than once. Direct `detail.promise = …` keeps working alongside it.
28
+ */
29
+ export declare function attachWaitUntil(detail: Awaitable): void;
30
+ /**
31
+ * Register an async content handler on a CustomEvent.
32
+ * The handler receives the target element and an AbortSignal.
33
+ * If it returns a Promise, it is handed to event.detail.waitUntil() so the
34
+ * consuming code awaits it (combining with any other waitUntil calls).
35
+ * Pass once:true to skip the handler after the first successful load
36
+ * (tracked via target.dataset.loaded).
37
+ */
38
+ export declare function registerBeforeOpenHandler<D extends Awaitable & {
39
+ signal: AbortSignal;
40
+ }>(element: HTMLElement, eventName: string, getTarget: (detail: D) => HTMLElement, handler: (target: HTMLElement, signal: AbortSignal) => Promise<void> | void, options?: {
41
+ once?: boolean;
42
+ }): void;