@campminder/components 0.1.0 → 0.3.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/README.md +36 -0
- package/dist/{property-B5JHHBCF.js → shell-tokens-BjECzZvL.js} +47 -43
- package/dist/shell-tokens.d.ts +39 -0
- package/dist/shell-tokens.test.d.ts +1 -0
- package/dist/side-nav/MegaMenu.d.ts +9 -1
- package/dist/side-nav/MegaMenuMobile.d.ts +46 -0
- package/dist/side-nav/MegaMenuMobile.styles.d.ts +11 -0
- package/dist/side-nav/SideNav.d.ts +111 -3
- package/dist/side-nav/SideNav.styles.d.ts +9 -0
- package/dist/side-nav/Utils/render-leaf.d.ts +3 -0
- package/dist/side-nav/mega-menu-focus.test.d.ts +1 -0
- package/dist/side-nav/mega-menu-isolation.test.d.ts +1 -0
- package/dist/side-nav/mega-menu-layout.d.ts +11 -0
- package/dist/side-nav/mega-menu-mobile-focus.test.d.ts +1 -0
- package/dist/side-nav/mega-menu-mobile.test.d.ts +1 -0
- package/dist/side-nav/side-nav-styles-cascade.test.d.ts +1 -0
- package/dist/side-nav.js +880 -326
- package/dist/{state-Ns9O6R8g.js → state-BvQoY71J.js} +1 -1
- package/dist/super-user-banner.js +16 -10
- package/dist/top-bar.js +85 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @campminder/components
|
|
2
2
|
|
|
3
|
+
> Published on public npm. Install and render: [`docs/consuming-the-shell.md`](../../docs/consuming-the-shell.md).
|
|
4
|
+
> Working in here: [`CONTRIBUTING.md`](../../CONTRIBUTING.md).
|
|
5
|
+
|
|
3
6
|
The Campminder chrome: left nav, top bar, super-user banner. Lit web components,
|
|
4
7
|
composition-agnostic, rendering only what they are handed.
|
|
5
8
|
|
|
@@ -196,3 +199,36 @@ fails loudly rather than silently rendering an empty banner.
|
|
|
196
199
|
|
|
197
200
|
> Components render themselves; the shell owns layout. No component may touch
|
|
198
201
|
> `document.body`, `document.head`, or query for a sibling.
|
|
202
|
+
|
|
203
|
+
## Dismissing the side-nav flyout
|
|
204
|
+
|
|
205
|
+
`<cm-side-nav>` exposes exactly one dismissal entry point:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
dismissFlyout(restoreFocus: boolean): void
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
It closes the flyout when one is open and is a no-op when none is. Every trigger
|
|
212
|
+
routes through it — the flyout's own Escape and shift-Tab paths, the navigation's
|
|
213
|
+
mouseleave, the `cm-mega-menu-close` event (which now carries
|
|
214
|
+
`detail.restoreFocus`), and the host shell's page-level Escape and outside pointer
|
|
215
|
+
press — so the focus rule below is applied in one place and no trigger can bypass
|
|
216
|
+
it.
|
|
217
|
+
|
|
218
|
+
**The focus rule.** Focus moves only when `restoreFocus` is true _and_ focus is
|
|
219
|
+
currently inside the flyout; then it goes to the category control that opened the
|
|
220
|
+
flyout, falling back to the navigation's collapse control when that category
|
|
221
|
+
control no longer exists. Focus is never left on the document body, and is never
|
|
222
|
+
pulled into the navigation from somewhere else on the page — a person typing in a
|
|
223
|
+
form field, or pressing a pointer outside the flyout, keeps the focus they have.
|
|
224
|
+
|
|
225
|
+
Two consequences worth keeping in mind when changing this code:
|
|
226
|
+
|
|
227
|
+
- The focus target is read **before** the open-category state is cleared. The
|
|
228
|
+
target is found via the `category-hovered` class, which is derived from that
|
|
229
|
+
state; clearing first leaves nothing to find and silently reintroduces the
|
|
230
|
+
dropped-focus bug.
|
|
231
|
+
- Page-level Escape and outside-pointer listening belongs to the **shell**, which
|
|
232
|
+
calls `dismissFlyout` on each slotted navigation — `true` for Escape, `false`
|
|
233
|
+
for an outside pointer press. This component registers nothing on `document` or
|
|
234
|
+
`window`, per the rule above, and `mega-menu-isolation.test.ts` observes that.
|
|
@@ -22,7 +22,7 @@ let it = class {
|
|
|
22
22
|
return this.cssText;
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
-
const
|
|
25
|
+
const rt = (r) => new it(typeof r == "string" ? r : r + "", void 0, B), Mt = (r, ...t) => {
|
|
26
26
|
const e = r.length === 1 ? r[0] : t.reduce((s, i, o) => s + ((n) => {
|
|
27
27
|
if (n._$cssResult$ === !0) return n.cssText;
|
|
28
28
|
if (typeof n == "number") return n;
|
|
@@ -35,17 +35,17 @@ const at = (r) => new it(typeof r == "string" ? r : r + "", void 0, B), Ot = (r,
|
|
|
35
35
|
const s = document.createElement("style"), i = H.litNonce;
|
|
36
36
|
i !== void 0 && s.setAttribute("nonce", i), s.textContent = e.cssText, r.appendChild(s);
|
|
37
37
|
}
|
|
38
|
-
},
|
|
38
|
+
}, F = I ? (r) => r : (r) => r instanceof CSSStyleSheet ? ((t) => {
|
|
39
39
|
let e = "";
|
|
40
40
|
for (const s of t.cssRules) e += s.cssText;
|
|
41
|
-
return
|
|
41
|
+
return rt(e);
|
|
42
42
|
})(r) : r;
|
|
43
43
|
/**
|
|
44
44
|
* @license
|
|
45
45
|
* Copyright 2017 Google LLC
|
|
46
46
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
47
47
|
*/
|
|
48
|
-
const { is: ct, defineProperty: dt, getOwnPropertyDescriptor: pt, getOwnPropertyNames: ut, getOwnPropertySymbols: $t, getPrototypeOf: _t } = Object, f = globalThis,
|
|
48
|
+
const { is: ct, defineProperty: dt, getOwnPropertyDescriptor: pt, getOwnPropertyNames: ut, getOwnPropertySymbols: $t, getPrototypeOf: _t } = Object, f = globalThis, Z = f.trustedTypes, ft = Z ? Z.emptyScript : "", k = f.reactiveElementPolyfillSupport, w = (r, t) => r, N = { toAttribute(r, t) {
|
|
49
49
|
switch (t) {
|
|
50
50
|
case Boolean:
|
|
51
51
|
r = r ? ft : null;
|
|
@@ -73,7 +73,7 @@ const { is: ct, defineProperty: dt, getOwnPropertyDescriptor: pt, getOwnProperty
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
return e;
|
|
76
|
-
} },
|
|
76
|
+
} }, V = (r, t) => !ct(r, t), J = { attribute: !0, type: String, converter: N, reflect: !1, useDefault: !1, hasChanged: V };
|
|
77
77
|
Symbol.metadata ?? (Symbol.metadata = Symbol("metadata")), f.litPropertyMetadata ?? (f.litPropertyMetadata = /* @__PURE__ */ new WeakMap());
|
|
78
78
|
let v = class extends HTMLElement {
|
|
79
79
|
static addInitializer(t) {
|
|
@@ -82,7 +82,7 @@ let v = class extends HTMLElement {
|
|
|
82
82
|
static get observedAttributes() {
|
|
83
83
|
return this.finalize(), this._$Eh && [...this._$Eh.keys()];
|
|
84
84
|
}
|
|
85
|
-
static createProperty(t, e =
|
|
85
|
+
static createProperty(t, e = J) {
|
|
86
86
|
if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) {
|
|
87
87
|
const s = Symbol(), i = this.getPropertyDescriptor(t, s, e);
|
|
88
88
|
i !== void 0 && dt(this.prototype, t, i);
|
|
@@ -100,7 +100,7 @@ let v = class extends HTMLElement {
|
|
|
100
100
|
}, configurable: !0, enumerable: !0 };
|
|
101
101
|
}
|
|
102
102
|
static getPropertyOptions(t) {
|
|
103
|
-
return this.elementProperties.get(t) ??
|
|
103
|
+
return this.elementProperties.get(t) ?? J;
|
|
104
104
|
}
|
|
105
105
|
static _$Ei() {
|
|
106
106
|
if (this.hasOwnProperty(w("elementProperties"))) return;
|
|
@@ -129,8 +129,8 @@ let v = class extends HTMLElement {
|
|
|
129
129
|
const e = [];
|
|
130
130
|
if (Array.isArray(t)) {
|
|
131
131
|
const s = new Set(t.flat(1 / 0).reverse());
|
|
132
|
-
for (const i of s) e.unshift(
|
|
133
|
-
} else t !== void 0 && e.push(
|
|
132
|
+
for (const i of s) e.unshift(F(i));
|
|
133
|
+
} else t !== void 0 && e.push(F(t));
|
|
134
134
|
return e;
|
|
135
135
|
}
|
|
136
136
|
static _$Eu(t, e) {
|
|
@@ -202,7 +202,7 @@ let v = class extends HTMLElement {
|
|
|
202
202
|
var n;
|
|
203
203
|
if (t !== void 0) {
|
|
204
204
|
const a = this.constructor;
|
|
205
|
-
if (i === !1 && (o = this[t]), s ?? (s = a.getPropertyOptions(t)), !((s.hasChanged ??
|
|
205
|
+
if (i === !1 && (o = this[t]), s ?? (s = a.getPropertyOptions(t)), !((s.hasChanged ?? V)(o, e) || s.useDefault && s.reflect && o === ((n = this._$Ej) == null ? void 0 : n.get(t)) && !this.hasAttribute(a._$Eu(t, s)))) return;
|
|
206
206
|
this.C(t, e, s);
|
|
207
207
|
}
|
|
208
208
|
this.isUpdatePending === !1 && (this._$ES = this._$EP());
|
|
@@ -278,18 +278,18 @@ let v = class extends HTMLElement {
|
|
|
278
278
|
firstUpdated(t) {
|
|
279
279
|
}
|
|
280
280
|
};
|
|
281
|
-
v.elementStyles = [], v.shadowRootOptions = { mode: "open" }, v[w("elementProperties")] = /* @__PURE__ */ new Map(), v[w("finalized")] = /* @__PURE__ */ new Map(),
|
|
281
|
+
v.elementStyles = [], v.shadowRootOptions = { mode: "open" }, v[w("elementProperties")] = /* @__PURE__ */ new Map(), v[w("finalized")] = /* @__PURE__ */ new Map(), k == null || k({ ReactiveElement: v }), (f.reactiveElementVersions ?? (f.reactiveElementVersions = [])).push("2.1.2");
|
|
282
282
|
/**
|
|
283
283
|
* @license
|
|
284
284
|
* Copyright 2017 Google LLC
|
|
285
285
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
286
286
|
*/
|
|
287
|
-
const C = globalThis,
|
|
288
|
-
\f\r]`, b = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,
|
|
289
|
-
\f\r"'\`<>=]|("|')|))|$)`, "g"),
|
|
290
|
-
function
|
|
291
|
-
if (!
|
|
292
|
-
return
|
|
287
|
+
const C = globalThis, K = (r) => r, R = C.trustedTypes, Y = R ? R.createPolicy("lit-html", { createHTML: (r) => r }) : void 0, nt = "$lit$", _ = `lit$${Math.random().toFixed(9).slice(2)}$`, ot = "?" + _, At = `<${ot}>`, g = document, P = () => g.createComment(""), U = (r) => r === null || typeof r != "object" && typeof r != "function", q = Array.isArray, mt = (r) => q(r) || typeof (r == null ? void 0 : r[Symbol.iterator]) == "function", D = `[
|
|
288
|
+
\f\r]`, b = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, G = /-->/g, Q = />/g, A = RegExp(`>|${D}(?:([^\\s"'>=/]+)(${D}*=${D}*(?:[^
|
|
289
|
+
\f\r"'\`<>=]|("|')|))|$)`, "g"), X = /'/g, tt = /"/g, ht = /^(?:script|style|textarea|title)$/i, yt = (r) => (t, ...e) => ({ _$litType$: r, strings: t, values: e }), Tt = yt(1), E = Symbol.for("lit-noChange"), d = Symbol.for("lit-nothing"), et = /* @__PURE__ */ new WeakMap(), m = g.createTreeWalker(g, 129);
|
|
290
|
+
function at(r, t) {
|
|
291
|
+
if (!q(r) || !r.hasOwnProperty("raw")) throw Error("invalid template strings array");
|
|
292
|
+
return Y !== void 0 ? Y.createHTML(t) : t;
|
|
293
293
|
}
|
|
294
294
|
const gt = (r, t) => {
|
|
295
295
|
const e = r.length - 1, s = [];
|
|
@@ -297,11 +297,11 @@ const gt = (r, t) => {
|
|
|
297
297
|
for (let a = 0; a < e; a++) {
|
|
298
298
|
const h = r[a];
|
|
299
299
|
let c, p, l = -1, u = 0;
|
|
300
|
-
for (; u < h.length && (n.lastIndex = u, p = n.exec(h), p !== null); ) u = n.lastIndex, n === b ? p[1] === "!--" ? n =
|
|
300
|
+
for (; u < h.length && (n.lastIndex = u, p = n.exec(h), p !== null); ) u = n.lastIndex, n === b ? p[1] === "!--" ? n = G : p[1] !== void 0 ? n = Q : p[2] !== void 0 ? (ht.test(p[2]) && (i = RegExp("</" + p[2], "g")), n = A) : p[3] !== void 0 && (n = A) : n === A ? p[0] === ">" ? (n = i ?? b, l = -1) : p[1] === void 0 ? l = -2 : (l = n.lastIndex - p[2].length, c = p[1], n = p[3] === void 0 ? A : p[3] === '"' ? tt : X) : n === tt || n === X ? n = A : n === G || n === Q ? n = b : (n = A, i = void 0);
|
|
301
301
|
const $ = n === A && r[a + 1].startsWith("/>") ? " " : "";
|
|
302
|
-
o += n === b ? h + At : l >= 0 ? (s.push(c), h.slice(0, l) +
|
|
302
|
+
o += n === b ? h + At : l >= 0 ? (s.push(c), h.slice(0, l) + nt + h.slice(l) + _ + $) : h + _ + (l === -2 ? a : $);
|
|
303
303
|
}
|
|
304
|
-
return [
|
|
304
|
+
return [at(r, o + (r[e] || "<?>") + (t === 2 ? "</svg>" : t === 3 ? "</math>" : "")), s];
|
|
305
305
|
};
|
|
306
306
|
class x {
|
|
307
307
|
constructor({ strings: t, _$litType$: e }, s) {
|
|
@@ -315,11 +315,11 @@ class x {
|
|
|
315
315
|
}
|
|
316
316
|
for (; (i = m.nextNode()) !== null && h.length < a; ) {
|
|
317
317
|
if (i.nodeType === 1) {
|
|
318
|
-
if (i.hasAttributes()) for (const l of i.getAttributeNames()) if (l.endsWith(
|
|
318
|
+
if (i.hasAttributes()) for (const l of i.getAttributeNames()) if (l.endsWith(nt)) {
|
|
319
319
|
const u = p[n++], $ = i.getAttribute(l).split(_), M = /([.?@])?(.*)/.exec(u);
|
|
320
|
-
h.push({ type: 1, index: o, name: M[2], strings: $, ctor: M[1] === "." ? Et : M[1] === "?" ? St : M[1] === "@" ? bt :
|
|
320
|
+
h.push({ type: 1, index: o, name: M[2], strings: $, ctor: M[1] === "." ? Et : M[1] === "?" ? St : M[1] === "@" ? bt : L }), i.removeAttribute(l);
|
|
321
321
|
} else l.startsWith(_) && (h.push({ type: 6, index: o }), i.removeAttribute(l));
|
|
322
|
-
if (
|
|
322
|
+
if (ht.test(i.tagName)) {
|
|
323
323
|
const l = i.textContent.split(_), u = l.length - 1;
|
|
324
324
|
if (u > 0) {
|
|
325
325
|
i.textContent = R ? R.emptyScript : "";
|
|
@@ -327,7 +327,7 @@ class x {
|
|
|
327
327
|
i.append(l[u], P());
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
|
-
} else if (i.nodeType === 8) if (i.data ===
|
|
330
|
+
} else if (i.nodeType === 8) if (i.data === ot) h.push({ type: 2, index: o });
|
|
331
331
|
else {
|
|
332
332
|
let l = -1;
|
|
333
333
|
for (; (l = i.data.indexOf(_, l + 1)) !== -1; ) h.push({ type: 7, index: o }), l += _.length - 1;
|
|
@@ -408,7 +408,7 @@ class O {
|
|
|
408
408
|
}
|
|
409
409
|
$(t) {
|
|
410
410
|
var o;
|
|
411
|
-
const { values: e, _$litType$: s } = t, i = typeof s == "number" ? this._$AC(t) : (s.el === void 0 && (s.el = x.createElement(
|
|
411
|
+
const { values: e, _$litType$: s } = t, i = typeof s == "number" ? this._$AC(t) : (s.el === void 0 && (s.el = x.createElement(at(s.h, s.h[0]), this.options)), s);
|
|
412
412
|
if (((o = this._$AH) == null ? void 0 : o._$AD) === i) this._$AH.p(e);
|
|
413
413
|
else {
|
|
414
414
|
const n = new vt(i, this), a = n.u(this.options);
|
|
@@ -420,7 +420,7 @@ class O {
|
|
|
420
420
|
return e === void 0 && et.set(t.strings, e = new x(t)), e;
|
|
421
421
|
}
|
|
422
422
|
k(t) {
|
|
423
|
-
|
|
423
|
+
q(this._$AH) || (this._$AH = [], this._$AR());
|
|
424
424
|
const e = this._$AH;
|
|
425
425
|
let s, i = 0;
|
|
426
426
|
for (const o of t) i === e.length ? e.push(s = new O(this.O(P()), this.O(P()), this, this.options)) : s = e[i], s._$AI(o), i++;
|
|
@@ -429,8 +429,8 @@ class O {
|
|
|
429
429
|
_$AR(t = this._$AA.nextSibling, e) {
|
|
430
430
|
var s;
|
|
431
431
|
for ((s = this._$AP) == null ? void 0 : s.call(this, !1, !0, e); t !== this._$AB; ) {
|
|
432
|
-
const i =
|
|
433
|
-
|
|
432
|
+
const i = K(t).nextSibling;
|
|
433
|
+
K(t).remove(), t = i;
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
436
|
setConnected(t) {
|
|
@@ -438,7 +438,7 @@ class O {
|
|
|
438
438
|
this._$AM === void 0 && (this._$Cv = t, (e = this._$AP) == null || e.call(this, t));
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
|
-
class
|
|
441
|
+
class L {
|
|
442
442
|
get tagName() {
|
|
443
443
|
return this.element.tagName;
|
|
444
444
|
}
|
|
@@ -463,7 +463,7 @@ class k {
|
|
|
463
463
|
t === d ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? "");
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
|
-
class Et extends
|
|
466
|
+
class Et extends L {
|
|
467
467
|
constructor() {
|
|
468
468
|
super(...arguments), this.type = 3;
|
|
469
469
|
}
|
|
@@ -471,7 +471,7 @@ class Et extends k {
|
|
|
471
471
|
this.element[this.name] = t === d ? void 0 : t;
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
|
-
class St extends
|
|
474
|
+
class St extends L {
|
|
475
475
|
constructor() {
|
|
476
476
|
super(...arguments), this.type = 4;
|
|
477
477
|
}
|
|
@@ -479,7 +479,7 @@ class St extends k {
|
|
|
479
479
|
this.element.toggleAttribute(this.name, !!t && t !== d);
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
|
-
class bt extends
|
|
482
|
+
class bt extends L {
|
|
483
483
|
constructor(t, e, s, i, o) {
|
|
484
484
|
super(t, e, s, i, o), this.type = 5;
|
|
485
485
|
}
|
|
@@ -504,8 +504,8 @@ class wt {
|
|
|
504
504
|
S(this, t);
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
|
-
const
|
|
508
|
-
|
|
507
|
+
const j = C.litHtmlPolyfillSupport;
|
|
508
|
+
j == null || j(x, O), (C.litHtmlVersions ?? (C.litHtmlVersions = [])).push("3.3.3");
|
|
509
509
|
const Ct = (r, t, e) => {
|
|
510
510
|
const s = (e == null ? void 0 : e.renderBefore) ?? t;
|
|
511
511
|
let i = s._$litPart$;
|
|
@@ -548,15 +548,15 @@ class T extends v {
|
|
|
548
548
|
}
|
|
549
549
|
var st;
|
|
550
550
|
T._$litElement$ = !0, T.finalized = !0, (st = y.litElementHydrateSupport) == null || st.call(y, { LitElement: T });
|
|
551
|
-
const
|
|
552
|
-
|
|
551
|
+
const z = y.litElementPolyfillSupport;
|
|
552
|
+
z == null || z({ LitElement: T });
|
|
553
553
|
(y.litElementVersions ?? (y.litElementVersions = [])).push("4.2.2");
|
|
554
554
|
/**
|
|
555
555
|
* @license
|
|
556
556
|
* Copyright 2017 Google LLC
|
|
557
557
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
558
558
|
*/
|
|
559
|
-
const
|
|
559
|
+
const Nt = (r) => (t, e) => {
|
|
560
560
|
e !== void 0 ? e.addInitializer(() => {
|
|
561
561
|
customElements.define(r, t);
|
|
562
562
|
}) : customElements.define(r, t);
|
|
@@ -566,7 +566,7 @@ const Tt = (r) => (t, e) => {
|
|
|
566
566
|
* Copyright 2017 Google LLC
|
|
567
567
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
568
568
|
*/
|
|
569
|
-
const Pt = { attribute: !0, type: String, converter: N, reflect: !1, hasChanged:
|
|
569
|
+
const Pt = { attribute: !0, type: String, converter: N, reflect: !1, hasChanged: V }, Ut = (r = Pt, t, e) => {
|
|
570
570
|
const { kind: s, metadata: i } = e;
|
|
571
571
|
let o = globalThis.litPropertyMetadata.get(i);
|
|
572
572
|
if (o === void 0 && globalThis.litPropertyMetadata.set(i, o = /* @__PURE__ */ new Map()), s === "setter" && ((r = Object.create(r)).wrapped = !0), o.set(e.name, r), s === "accessor") {
|
|
@@ -587,17 +587,21 @@ const Pt = { attribute: !0, type: String, converter: N, reflect: !1, hasChanged:
|
|
|
587
587
|
}
|
|
588
588
|
throw Error("Unsupported decorator location: " + s);
|
|
589
589
|
};
|
|
590
|
-
function
|
|
590
|
+
function Rt(r) {
|
|
591
591
|
return (t, e) => typeof e == "object" ? Ut(r, t, e) : ((s, i, o) => {
|
|
592
592
|
const n = i.hasOwnProperty(o);
|
|
593
593
|
return i.constructor.createProperty(o, s), n ? Object.getOwnPropertyDescriptor(i, o) : void 0;
|
|
594
594
|
})(r, t, e);
|
|
595
595
|
}
|
|
596
|
+
const xt = "--cm-shell-font-family", Lt = rt(
|
|
597
|
+
`var(${xt}, 'General Sans', system-ui, sans-serif)`
|
|
598
|
+
);
|
|
596
599
|
export {
|
|
597
600
|
d as A,
|
|
601
|
+
Lt as S,
|
|
598
602
|
T as a,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
+
Tt as b,
|
|
604
|
+
Mt as i,
|
|
605
|
+
Rt as n,
|
|
606
|
+
Nt as t
|
|
603
607
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell-wide design tokens shared by every chrome component's stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* `mega-menu-layout.ts` is the precedent: a value two or more stylesheets must
|
|
5
|
+
* agree on gets exactly one home, because the failure mode of disagreement is
|
|
6
|
+
* silent. A font is worse than a width that way — a stale copy renders, it just
|
|
7
|
+
* renders wrong, and nothing in the build notices.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The custom property a host document sets to choose the shell's typeface.
|
|
11
|
+
*
|
|
12
|
+
* Exported as a string (not just baked into {@link SHELL_FONT_FAMILY}) so the
|
|
13
|
+
* guard script and tests can assert against one spelling instead of a literal
|
|
14
|
+
* they'd have to keep in sync by hand.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SHELL_FONT_FAMILY_VAR = "--cm-shell-font-family";
|
|
17
|
+
/**
|
|
18
|
+
* The shell's font stack, as every component's `:host` should declare it.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately a `var()` with a fallback chain rather than a literal. Two
|
|
21
|
+
* properties of that shape are load-bearing:
|
|
22
|
+
*
|
|
23
|
+
* 1. **It only ever reads `--cm-shell-font-family`, never assigns it.** A host
|
|
24
|
+
* that has not loaded General Sans sets the property and wins. If any
|
|
25
|
+
* `:host` rule in this package assigned it instead, that declaration would
|
|
26
|
+
* beat the host's and the override would be dead on arrival.
|
|
27
|
+
* 2. **Naming General Sans here, rather than shipping the font, is the whole
|
|
28
|
+
* mechanism.** `@font-face` is document-scoped, not shadow-scoped, so faces
|
|
29
|
+
* the host registers are already reachable from inside these shadow roots —
|
|
30
|
+
* the shell only has to ask for them by name. This is the same conclusion
|
|
31
|
+
* `SideNav.styles.ts`'s port notes reached when they deleted the upstream
|
|
32
|
+
* `@import url(fonts.googleapis.com/...)`: a font is something the shell
|
|
33
|
+
* should provide, not something a component fetches for itself.
|
|
34
|
+
*
|
|
35
|
+
* Vendoring the font files here would undo both. The consumer already
|
|
36
|
+
* self-hosts them, and a second copy re-inherits the base-path 404 that
|
|
37
|
+
* AB#26011 fixed downstream.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SHELL_FONT_FAMILY: import('lit').CSSResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -25,6 +25,15 @@ export declare class MegaMenu extends LitElement {
|
|
|
25
25
|
disconnectedCallback(): void;
|
|
26
26
|
private handleKeyDown;
|
|
27
27
|
private get categoryData();
|
|
28
|
+
/**
|
|
29
|
+
* The close event keeps its name, `bubbles` and `composed` flags; `detail` is
|
|
30
|
+
* new and carries the caller's focus intent onward to `<cm-side-nav>`'s
|
|
31
|
+
* `dismissFlyout()`, which is the one place the focus rule is applied. Both
|
|
32
|
+
* keyboard paths ask for restoration — they are the paths where focus is inside
|
|
33
|
+
* the flyout and would otherwise be dropped on the document body. The close
|
|
34
|
+
* button asks too; it is harmless when focus is not inside, because
|
|
35
|
+
* `dismissFlyout` only moves focus that actually sits in the flyout.
|
|
36
|
+
*/
|
|
28
37
|
private handleCloseMegaMenu;
|
|
29
38
|
/**
|
|
30
39
|
* Packs menu items into as many 248px columns as the container's own
|
|
@@ -40,7 +49,6 @@ export declare class MegaMenu extends LitElement {
|
|
|
40
49
|
*/
|
|
41
50
|
private setMenuItemsContainerWidth;
|
|
42
51
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
43
|
-
private renderLeaf;
|
|
44
52
|
render(): import('lit').TemplateResult<1> | null;
|
|
45
53
|
}
|
|
46
54
|
declare global {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { MenuTree } from '@campminder/menu-tree';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `<cm-mega-menu-mobile>` — the mobile drill-down for a category with
|
|
6
|
+
* sub-categories, built new for Floor 5D (AB#26231) rather than ported.
|
|
7
|
+
*
|
|
8
|
+
* Upstream's `MegaMenuMobile` drove this through `MobileModalController`
|
|
9
|
+
* (rewrites `meta[name=viewport]`, five `document`-level touch listeners,
|
|
10
|
+
* `document.body` scroll-lock) and `showMobileSubmenu` / `activeMobileCategory`
|
|
11
|
+
* state that Floor 4 never carried over. None of that comes across — see
|
|
12
|
+
* `AGENTS.md`'s one rule and the plan's Floor 5D section. This component only
|
|
13
|
+
* renders the content for whichever category `<cm-side-nav>` has drilled into;
|
|
14
|
+
* `<cm-side-nav>` owns the `activeMobileCategoryId` state and the overlay/backdrop
|
|
15
|
+
* chrome around it, the same split `<cm-mega-menu>` has with its hover state.
|
|
16
|
+
*
|
|
17
|
+
* `tree` + `categoryId` match `<cm-mega-menu>`'s shape deliberately — matching on
|
|
18
|
+
* `id`, never on the terminology-resolved `label`.
|
|
19
|
+
*/
|
|
20
|
+
export declare class MegaMenuMobile extends LitElement {
|
|
21
|
+
static readonly styles: import('lit').CSSResult[];
|
|
22
|
+
tree: MenuTree;
|
|
23
|
+
categoryId: string;
|
|
24
|
+
connectedCallback(): void;
|
|
25
|
+
disconnectedCallback(): void;
|
|
26
|
+
private handleKeyDown;
|
|
27
|
+
private get categoryData();
|
|
28
|
+
/**
|
|
29
|
+
* Requests the host go back to the main mobile menu list. Named `-back`, not
|
|
30
|
+
* `-close`, because it does not dismiss the mobile overlay itself — only
|
|
31
|
+
* `<cm-side-nav>`'s own toggle/backdrop does that (see its doc comment).
|
|
32
|
+
*/
|
|
33
|
+
private handleBack;
|
|
34
|
+
/**
|
|
35
|
+
* Required, not decorative: drilling in removes the category button focus
|
|
36
|
+
* was on from the DOM (it belonged to the main list, now unmounted), so
|
|
37
|
+
* without this, focus is dropped onto the document body.
|
|
38
|
+
*/
|
|
39
|
+
firstUpdated(): void;
|
|
40
|
+
render(): import('lit').TemplateResult<1> | null;
|
|
41
|
+
}
|
|
42
|
+
declare global {
|
|
43
|
+
interface HTMLElementTagNameMap {
|
|
44
|
+
'cm-mega-menu-mobile': MegaMenuMobile;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styles for `<cm-mega-menu-mobile>`, new for Floor 5D (AB#26231) — see that
|
|
3
|
+
* file's doc comment for why this is a rewrite, not a port.
|
|
4
|
+
*
|
|
5
|
+
* `--color-section-bg` is one of the four tokens the plan reserved on
|
|
6
|
+
* `SideNavStyles`'s `:host` for this floor (verified 2026-09-04 that upstream's
|
|
7
|
+
* only consumers of it are the mobile stylesheets); it pierces the shadow
|
|
8
|
+
* boundary from `<cm-side-nav>` the same way `--cm-side-nav-width` does for
|
|
9
|
+
* `MegaMenuStyles`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MegaMenuMobileStyles: import('lit').CSSResult;
|
|
@@ -5,13 +5,58 @@ import { MenuTree } from '@campminder/menu-tree';
|
|
|
5
5
|
* `<cm-side-nav>` — the left navigation.
|
|
6
6
|
*
|
|
7
7
|
* Ported 2026-09-02 from core-js's `SideNav.ts` (`<side-nav>`), core rendering path,
|
|
8
|
-
* with the desktop mega-menu flyout added 2026-09-04 (Floor 5)
|
|
8
|
+
* with the desktop mega-menu flyout added 2026-09-04 (Floor 5), and the mobile
|
|
9
|
+
* drill-down variant added 2026-09-17 (Floor 5D, AB#26231) — both mounted inside
|
|
9
10
|
* this component's own shadow root, not slotted; see the render() seam note below.
|
|
10
|
-
*
|
|
11
|
+
* The search-bar integration remains out of scope.
|
|
12
|
+
*
|
|
13
|
+
* Floor 5D renders both the desktop flyout and the mobile drill-down on every
|
|
14
|
+
* render and lets `SideNavStyles`'s `@media (max-width: ${MOBILE_BREAKPOINT_PX}px)`
|
|
15
|
+
* decide which is displayed — see that file's header. `mega-menu-layout.ts`'s
|
|
16
|
+
* `MOBILE_BREAKPOINT_PX` is the one place that number lives; both that CSS rule and
|
|
17
|
+
* the `matchMedia` query below read it, so they cannot silently disagree about
|
|
18
|
+
* where the line is.
|
|
19
|
+
*
|
|
20
|
+
* The inactive variant gets `aria-hidden` from `isNarrowViewport`, which
|
|
21
|
+
* `initMobileMediaQuery()`/`teardownMobileMediaQuery()` (below) keep live via a
|
|
22
|
+
* `matchMedia` `change` listener — a first pass at this floor used a one-time
|
|
23
|
+
* `window.innerWidth` read instead, which is wrong: CSS re-evaluates its media
|
|
24
|
+
* query continuously, so a viewport crossing the breakpoint after first render
|
|
25
|
+
* (a desktop resize, a tablet rotation) would have left `aria-hidden` pointing at
|
|
26
|
+
* the *previous* state, exposing the hidden variant to assistive technology and
|
|
27
|
+
* suppressing the visible one — worse than omitting the attribute. `matchMedia`
|
|
28
|
+
* here drives only that attribute; it does not gate which variant is
|
|
29
|
+
* *constructed* (both still always render) and is not what decides which one is
|
|
30
|
+
* visually shown (the CSS media query still is). Guarded for `matchMedia` being
|
|
31
|
+
* absent — true of this repo's jsdom, and kept defensive elsewhere — by falling
|
|
32
|
+
* back to the same static `window.innerWidth` read, un-updating, rather than
|
|
33
|
+
* throwing. The listener is added in `connectedCallback` and removed in
|
|
34
|
+
* `disconnectedCallback`, so it never outlives the element and does not touch
|
|
35
|
+
* `document`/`window`/`document.body` — it targets the `MediaQueryList` object
|
|
36
|
+
* `matchMedia()` returns, not those globals, so it does not trip AC-8's
|
|
37
|
+
* isolation guard (`mega-menu-isolation.test.ts`).
|
|
11
38
|
*/
|
|
12
39
|
export declare class SideNav extends LitElement {
|
|
13
40
|
static readonly styles: import('lit').CSSResult[];
|
|
14
41
|
private hoveredCategory;
|
|
42
|
+
/**
|
|
43
|
+
* Floor 5D's drill-down state machine, built from scratch — Floor 4 never
|
|
44
|
+
* ported `showMobileSubmenu` / `activeMobileCategory`, so there was nothing
|
|
45
|
+
* here to adapt. `mobileMenuOpen` is the mobile overlay itself (opened from
|
|
46
|
+
* `.menu-toggle`); `activeMobileCategoryId` is which category, if any, the
|
|
47
|
+
* overlay has drilled into. Deliberately two separate booleans/ids rather
|
|
48
|
+
* than one union, so "overlay open, showing the main list" and "overlay
|
|
49
|
+
* open, showing a drill-down" are both representable without a sentinel.
|
|
50
|
+
*/
|
|
51
|
+
private mobileMenuOpen;
|
|
52
|
+
private activeMobileCategoryId;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the mobile variant is the one CSS is currently showing. Kept live by
|
|
55
|
+
* `matchMedia`'s `change` event where available (see this file's header
|
|
56
|
+
* comment); the initializer is also the fallback value used when it is not.
|
|
57
|
+
*/
|
|
58
|
+
private isNarrowViewport;
|
|
59
|
+
private mobileMediaQuery;
|
|
15
60
|
private hoverTimeout;
|
|
16
61
|
/**
|
|
17
62
|
* Collapsed state lives on `<campminder-app-shell>`, keyed per portal — see its
|
|
@@ -60,24 +105,87 @@ export declare class SideNav extends LitElement {
|
|
|
60
105
|
* getter needs to cache.
|
|
61
106
|
*/
|
|
62
107
|
private get selectedCategory();
|
|
108
|
+
connectedCallback(): void;
|
|
63
109
|
disconnectedCallback(): void;
|
|
110
|
+
/** The fallback used where `matchMedia` is unavailable — see the class doc comment. */
|
|
111
|
+
private static readViewportWidthFallback;
|
|
112
|
+
private initMobileMediaQuery;
|
|
113
|
+
private teardownMobileMediaQuery;
|
|
114
|
+
private handleMobileMediaChange;
|
|
64
115
|
private toggleSidenav;
|
|
65
116
|
private setSelectedCategory;
|
|
66
117
|
private handleMenuItemHover;
|
|
67
118
|
private clearHoverTimeout;
|
|
68
119
|
private handleMouseEnter;
|
|
69
120
|
private handleMouseLeave;
|
|
121
|
+
/**
|
|
122
|
+
* The single dismissal entry point for the flyout — every trigger routes through
|
|
123
|
+
* here (the flyout's own Escape and shift-Tab paths, the nav's mouseleave, and
|
|
124
|
+
* the shell's page-level Escape / outside pointer press) so the focus rule is
|
|
125
|
+
* applied in exactly one place and no trigger can bypass it.
|
|
126
|
+
*
|
|
127
|
+
* `restoreFocus` is the caller's *intent*; focus only actually moves when it is
|
|
128
|
+
* also true that focus is currently inside the flyout. A person typing in a form
|
|
129
|
+
* field while the flyout is dismissed keeps their cursor where it is, and a
|
|
130
|
+
* pointer press outside keeps the focus the press itself produces.
|
|
131
|
+
*
|
|
132
|
+
* The focus target is read BEFORE `hoveredCategory` is cleared, and that order is
|
|
133
|
+
* load-bearing: the target is found via the `category-hovered` class, which is
|
|
134
|
+
* derived from `hoveredCategory` in `buildCategoryClass`. Clearing first would
|
|
135
|
+
* leave nothing to find and silently drop focus onto the document body — the very
|
|
136
|
+
* defect this method exists to fix.
|
|
137
|
+
*/
|
|
138
|
+
dismissFlyout(restoreFocus: boolean): void;
|
|
139
|
+
private focusIsInsideFlyout;
|
|
140
|
+
/**
|
|
141
|
+
* The category control the flyout was opened from, or the collapse control when
|
|
142
|
+
* that category control no longer exists — it always does, so focus can never
|
|
143
|
+
* land on the document body.
|
|
144
|
+
*/
|
|
145
|
+
private focusRestoreTarget;
|
|
70
146
|
private get hamburgerIcon();
|
|
71
147
|
/**
|
|
72
148
|
* The category the mega-menu flyout is open for, or `undefined` when none
|
|
73
149
|
* is. Guards on `renderableSubCategories(category).length > 0` (the same
|
|
74
|
-
* predicate `hasSubmenus` uses
|
|
150
|
+
* predicate `hasSubmenus` uses to render a category as a button) rather than on
|
|
75
151
|
* `hoveredCategory` alone — `hoveredCategory` can also be the literal
|
|
76
152
|
* string `'sidebar-toggle'` (see `handleMouseEnter` call sites), and a
|
|
77
153
|
* category with no renderable sub-categories must not open onto nothing.
|
|
78
154
|
*/
|
|
79
155
|
private get openCategory();
|
|
80
156
|
private buildCategoryClass;
|
|
157
|
+
/** The category the mobile drill-down is showing, mirroring `openCategory`. */
|
|
158
|
+
private get openMobileCategory();
|
|
159
|
+
private toggleMobileMenu;
|
|
160
|
+
/**
|
|
161
|
+
* The single dismissal entry point for the mobile overlay — every trigger
|
|
162
|
+
* (the backdrop click, `.menu-toggle`, the overlay's own Escape) routes
|
|
163
|
+
* through here, mirroring `dismissFlyout`'s reasoning, so the focus rule is
|
|
164
|
+
* applied in exactly one place and no trigger can bypass it.
|
|
165
|
+
*
|
|
166
|
+
* `restoreFocus` is the caller's intent; focus only actually moves when it is
|
|
167
|
+
* also true that focus is currently inside the overlay. The target is read
|
|
168
|
+
* BEFORE state is cleared, same load-bearing order as `dismissFlyout` — the
|
|
169
|
+
* restore target is `.menu-toggle`, which stays in the DOM regardless of
|
|
170
|
+
* `mobileMenuOpen`, but focus-inside-overlay is only answerable while the
|
|
171
|
+
* overlay is still open.
|
|
172
|
+
*/
|
|
173
|
+
dismissMobileMenu(restoreFocus: boolean): void;
|
|
174
|
+
private focusIsInsideMobileOverlay;
|
|
175
|
+
private openMobileSubmenu;
|
|
176
|
+
private closeMobileSubmenu;
|
|
177
|
+
private handleMobileOverlayKeyDown;
|
|
178
|
+
private buildMobileCategoryClass;
|
|
179
|
+
private getMobileCategoryHtml;
|
|
180
|
+
private renderMobileMainMenu;
|
|
181
|
+
/**
|
|
182
|
+
* The mobile overlay's body — either the main category list, or the
|
|
183
|
+
* drill-down for whichever category is active. `cm-mega-menu-mobile-back`
|
|
184
|
+
* is the drill-down asking to return to the list; it does not close the
|
|
185
|
+
* overlay (that's `.menu-toggle` / the backdrop), so it only clears
|
|
186
|
+
* `activeMobileCategoryId`.
|
|
187
|
+
*/
|
|
188
|
+
private renderMobileOverlayBody;
|
|
81
189
|
private getCategoryHtml;
|
|
82
190
|
private renderMainMenu;
|
|
83
191
|
render(): import('lit').TemplateResult<1>;
|
|
@@ -38,5 +38,14 @@
|
|
|
38
38
|
* 2026-09-04, Floor 5 added `position: relative` to `.nav-container` as the
|
|
39
39
|
* mega-menu flyout's positioning context. `.nav-main-content` was deliberately
|
|
40
40
|
* left at `overflow: hidden` — see that rule's own comment below for why.
|
|
41
|
+
*
|
|
42
|
+
* 2026-09-17, Floor 5D (AB#26231) added the `@media (max-width: 530px)` block
|
|
43
|
+
* that picks the mobile variant over the desktop one — the whole of what makes
|
|
44
|
+
* "both variants render, CSS chooses" true — and ungated `.menu-toggle` /
|
|
45
|
+
* `.toggle-mobile-btn` (parked since Floor 5) and `.chevron-right` (parked
|
|
46
|
+
* since Floor 4) for the mobile category list's own use. `--overlay-menu-width`,
|
|
47
|
+
* `--overlay-menu-max-width`, `--color-backdrop` and `--color-section-bg` were
|
|
48
|
+
* reserved on `:host` since Floor 5 for exactly this floor; they now size the
|
|
49
|
+
* mobile overlay panel and its backdrop.
|
|
41
50
|
*/
|
|
42
51
|
export declare const SideNavStyles: import('lit').CSSResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -7,3 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export declare const MENU_ITEM_WIDTH_PX = 248;
|
|
9
9
|
export declare const MENU_ITEM_GUTTER_PX = 16;
|
|
10
|
+
/**
|
|
11
|
+
* The desktop-flyout / mobile-drill-down breakpoint, Floor 5D (AB#26231).
|
|
12
|
+
* `SideNav.styles.ts`'s `@media (max-width: ${MOBILE_BREAKPOINT_PX}px)` (which
|
|
13
|
+
* variant is actually shown) and `SideNav.ts`'s `matchMedia` query (which one
|
|
14
|
+
* is marked `aria-hidden`) both read this one constant, so the two cannot
|
|
15
|
+
* silently disagree about where the line is — the same drift class the
|
|
16
|
+
* mega-menu constants above already guard against. Kept at upstream's `530`;
|
|
17
|
+
* revisit when `campminder-ui`'s breakpoints land (per the plan's Floor 5D
|
|
18
|
+
* ruling 3).
|
|
19
|
+
*/
|
|
20
|
+
export declare const MOBILE_BREAKPOINT_PX = 530;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|