@phcdevworks/spectre-components 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -1
- package/dist/index.cjs +205 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +205 -2
- package/dist/index.js.map +1 -1
- package/dist/nav-item.cjs +500 -0
- package/dist/nav-item.cjs.map +1 -0
- package/dist/nav-item.d.cts +74 -0
- package/dist/nav-item.d.ts +74 -0
- package/dist/nav-item.js +497 -0
- package/dist/nav-item.js.map +1 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ themselves — can consume Spectre without a framework-specific adapter.
|
|
|
16
16
|
| Project team | `project-design` |
|
|
17
17
|
| Repository role | Spectre L3a Lit web component layer |
|
|
18
18
|
| Package/artifact | `@phcdevworks/spectre-components` |
|
|
19
|
-
| Current version/status | 1.
|
|
19
|
+
| Current version/status | 1.12.0 |
|
|
20
20
|
|
|
21
21
|
## Standard Workflow
|
|
22
22
|
|
|
@@ -906,6 +906,48 @@ etc.).
|
|
|
906
906
|
|
|
907
907
|
---
|
|
908
908
|
|
|
909
|
+
### sp-nav-item
|
|
910
|
+
|
|
911
|
+
Renders a nav link that can optionally become a dropdown trigger, backed by
|
|
912
|
+
the same Spectre dropdown recipe as `sp-dropdown`. Place it inside `sp-nav`
|
|
913
|
+
alongside plain `<a>` links.
|
|
914
|
+
|
|
915
|
+
**Attributes**
|
|
916
|
+
|
|
917
|
+
| Attribute | Type | Default | Description |
|
|
918
|
+
| ------------------------- | ------- | ---------------- | ------------------------------------------------------------------- |
|
|
919
|
+
| `dropdown` | boolean | `false` | Renders a dropdown trigger + menu instead of a plain `<a>` |
|
|
920
|
+
| `href` | string | — | Link target when `dropdown` is `false` |
|
|
921
|
+
| `label` | string | — | Trigger/link text when no content is projected |
|
|
922
|
+
| `open` | boolean | `false` | Open/closed menu state (dropdown mode only) |
|
|
923
|
+
| `placement` | string | `'bottom-start'` | Menu position: `bottom-start`, `bottom-end`, `top-start`, `top-end` |
|
|
924
|
+
| `id` / `title` / `aria-*` | string | — | Forwarded to the rendered `<a>` or trigger `<button>` |
|
|
925
|
+
|
|
926
|
+
**Content projection** — in dropdown mode, children become the menu content;
|
|
927
|
+
a child with `slot="trigger"` becomes the trigger content instead of `label`.
|
|
928
|
+
Menu content can be a plain list of links or an `sp-grid` for a full mega-menu
|
|
929
|
+
layout. In link mode, children become the link's content.
|
|
930
|
+
|
|
931
|
+
**Events** — `sp-open` and `sp-close` (dropdown mode only), both `bubbles`.
|
|
932
|
+
|
|
933
|
+
**Internal targets** — `[data-sp-nav-item-trigger]` selects the trigger
|
|
934
|
+
button; `[data-sp-nav-item-menu]` selects the menu panel.
|
|
935
|
+
|
|
936
|
+
```html
|
|
937
|
+
<sp-nav>
|
|
938
|
+
<sp-nav-item href="/">Home</sp-nav-item>
|
|
939
|
+
<sp-nav-item dropdown label="Products">
|
|
940
|
+
<sp-grid columns="3" gap="lg">
|
|
941
|
+
<div>Column one</div>
|
|
942
|
+
<div>Column two</div>
|
|
943
|
+
<div>Column three</div>
|
|
944
|
+
</sp-grid>
|
|
945
|
+
</sp-nav-item>
|
|
946
|
+
</sp-nav>
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
909
951
|
### sp-sidebar
|
|
910
952
|
|
|
911
953
|
Renders an off-canvas `<aside>` with a toggle button and backdrop, backed by the
|
|
@@ -1169,6 +1211,7 @@ Each entry point registers only that component and exports only its surface:
|
|
|
1169
1211
|
| `.../section` | `sp-section` | `defineSpectreSection`, `SpectreSectionElement`, `SpectreSectionProps` |
|
|
1170
1212
|
| `.../stack` | `sp-stack` | `defineSpectreStack`, `SpectreStackElement`, stack constants and types |
|
|
1171
1213
|
| `.../nav` | `sp-nav` | `defineSpectreNav`, `SpectreNavElement`, `SpectreNavProps` |
|
|
1214
|
+
| `.../nav-item` | `sp-nav-item` | `defineSpectreNavItem`, `SpectreNavItemElement`, `SpectreNavItemProps` |
|
|
1172
1215
|
| `.../sidebar` | `sp-sidebar` | `defineSpectreSidebar`, `SpectreSidebarElement`, `SpectreSidebarProps` |
|
|
1173
1216
|
| `.../dropdown` | `sp-dropdown` | `defineSpectreDropdown`, `SpectreDropdownElement`, dropdown constants and types |
|
|
1174
1217
|
| `.../footer` | `sp-footer` | `defineSpectreFooter`, `SpectreFooterElement`, `SpectreFooterProps` |
|
package/dist/index.cjs
CHANGED
|
@@ -3452,6 +3452,208 @@ function defineSpectreNav(tagName = "sp-nav") {
|
|
|
3452
3452
|
customElements.define(tagName, SpectreNavElement);
|
|
3453
3453
|
return SpectreNavElement;
|
|
3454
3454
|
}
|
|
3455
|
+
var SpectreNavItemElement = class extends SpectreBaseElement {
|
|
3456
|
+
constructor() {
|
|
3457
|
+
super(...arguments);
|
|
3458
|
+
this.dropdown = false;
|
|
3459
|
+
this.href = void 0;
|
|
3460
|
+
this.label = void 0;
|
|
3461
|
+
this.open = false;
|
|
3462
|
+
this.placement = "bottom-start";
|
|
3463
|
+
this.triggerContent = [];
|
|
3464
|
+
this.menuContent = [];
|
|
3465
|
+
this.handleDocumentClick = (event) => {
|
|
3466
|
+
if (!this.open) {
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
if (!event.composedPath().includes(this)) {
|
|
3470
|
+
this.close();
|
|
3471
|
+
}
|
|
3472
|
+
};
|
|
3473
|
+
this.handleDocumentKeydown = (event) => {
|
|
3474
|
+
if (event.key === "Escape" && this.open) {
|
|
3475
|
+
this.close();
|
|
3476
|
+
this.triggerElement?.focus();
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
get id() {
|
|
3481
|
+
return super.id;
|
|
3482
|
+
}
|
|
3483
|
+
set id(value) {
|
|
3484
|
+
super.id = value;
|
|
3485
|
+
}
|
|
3486
|
+
get title() {
|
|
3487
|
+
return super.title;
|
|
3488
|
+
}
|
|
3489
|
+
set title(value) {
|
|
3490
|
+
super.title = value;
|
|
3491
|
+
}
|
|
3492
|
+
isInternalNode(node) {
|
|
3493
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
3494
|
+
return false;
|
|
3495
|
+
}
|
|
3496
|
+
const el = node;
|
|
3497
|
+
return el.hasAttribute("data-sp-nav-item-menu") || el.hasAttribute("data-sp-nav-item-trigger");
|
|
3498
|
+
}
|
|
3499
|
+
syncProjectedContent() {
|
|
3500
|
+
const nextTriggerContent = [];
|
|
3501
|
+
const nextMenuContent = [];
|
|
3502
|
+
this.childNodes.forEach((node) => {
|
|
3503
|
+
if (this.isInternalNode(node)) {
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
const isTriggerSlot = node.nodeType === Node.ELEMENT_NODE && node.getAttribute("slot") === "trigger";
|
|
3507
|
+
if (isTriggerSlot) {
|
|
3508
|
+
nextTriggerContent.push(node);
|
|
3509
|
+
} else {
|
|
3510
|
+
nextMenuContent.push(node);
|
|
3511
|
+
}
|
|
3512
|
+
});
|
|
3513
|
+
const changed = nextTriggerContent.length !== this.triggerContent.length || nextTriggerContent.some((n, i) => n !== this.triggerContent[i]) || nextMenuContent.length !== this.menuContent.length || nextMenuContent.some((n, i) => n !== this.menuContent[i]);
|
|
3514
|
+
if (changed) {
|
|
3515
|
+
this.triggerContent = nextTriggerContent;
|
|
3516
|
+
this.menuContent = nextMenuContent;
|
|
3517
|
+
}
|
|
3518
|
+
return changed;
|
|
3519
|
+
}
|
|
3520
|
+
get hasTriggerContent() {
|
|
3521
|
+
return hasMeaningfulContent(this.triggerContent);
|
|
3522
|
+
}
|
|
3523
|
+
get hasMenuContent() {
|
|
3524
|
+
return hasMeaningfulContent(this.menuContent);
|
|
3525
|
+
}
|
|
3526
|
+
connectedCallback() {
|
|
3527
|
+
super.connectedCallback();
|
|
3528
|
+
this.syncProjectedContent();
|
|
3529
|
+
this.startContentObserver();
|
|
3530
|
+
document.addEventListener("click", this.handleDocumentClick);
|
|
3531
|
+
document.addEventListener("keydown", this.handleDocumentKeydown);
|
|
3532
|
+
}
|
|
3533
|
+
disconnectedCallback() {
|
|
3534
|
+
this.stopContentObserver();
|
|
3535
|
+
document.removeEventListener("click", this.handleDocumentClick);
|
|
3536
|
+
document.removeEventListener("keydown", this.handleDocumentKeydown);
|
|
3537
|
+
super.disconnectedCallback();
|
|
3538
|
+
}
|
|
3539
|
+
update(changedProperties) {
|
|
3540
|
+
this.stopContentObserver();
|
|
3541
|
+
super.update(changedProperties);
|
|
3542
|
+
this.startContentObserver();
|
|
3543
|
+
}
|
|
3544
|
+
startContentObserver() {
|
|
3545
|
+
if (this.contentObserver) {
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3548
|
+
this.contentObserver = new MutationObserver(() => {
|
|
3549
|
+
if (this.syncProjectedContent()) {
|
|
3550
|
+
this.requestUpdate();
|
|
3551
|
+
}
|
|
3552
|
+
});
|
|
3553
|
+
this.contentObserver.observe(this, { childList: true });
|
|
3554
|
+
}
|
|
3555
|
+
stopContentObserver() {
|
|
3556
|
+
this.contentObserver?.disconnect();
|
|
3557
|
+
this.contentObserver = void 0;
|
|
3558
|
+
}
|
|
3559
|
+
willUpdate(changedProperties) {
|
|
3560
|
+
if (changedProperties.has("dropdown") && this.dropdown == null) {
|
|
3561
|
+
this.dropdown = false;
|
|
3562
|
+
}
|
|
3563
|
+
if (changedProperties.has("open") && this.open == null) {
|
|
3564
|
+
this.open = false;
|
|
3565
|
+
}
|
|
3566
|
+
if (changedProperties.has("placement") && (this.placement == null || !isDropdownPlacement(this.placement))) {
|
|
3567
|
+
this.placement = "bottom-start";
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
get triggerElement() {
|
|
3571
|
+
return this.querySelector("[data-sp-nav-item-trigger]");
|
|
3572
|
+
}
|
|
3573
|
+
toggle() {
|
|
3574
|
+
if (this.open) {
|
|
3575
|
+
this.close();
|
|
3576
|
+
} else {
|
|
3577
|
+
this.openMenu();
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
openMenu() {
|
|
3581
|
+
if (this.open) {
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
this.open = true;
|
|
3585
|
+
this.dispatchEvent(new CustomEvent("sp-open", { bubbles: true }));
|
|
3586
|
+
}
|
|
3587
|
+
close() {
|
|
3588
|
+
if (!this.open) {
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
this.open = false;
|
|
3592
|
+
this.dispatchEvent(new CustomEvent("sp-close", { bubbles: true }));
|
|
3593
|
+
}
|
|
3594
|
+
get wrapperClasses() {
|
|
3595
|
+
return spectreUi.getDropdownClasses();
|
|
3596
|
+
}
|
|
3597
|
+
get menuClasses() {
|
|
3598
|
+
return spectreUi.getDropdownMenuClasses({
|
|
3599
|
+
open: this.open ?? false,
|
|
3600
|
+
placement: this.placement
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
get navLinkClasses() {
|
|
3604
|
+
return spectreUi.getNavLinkClasses();
|
|
3605
|
+
}
|
|
3606
|
+
renderLink() {
|
|
3607
|
+
return lit.html`<a
|
|
3608
|
+
aria-label="${ifDefined_js.ifDefined(this.forwardedAriaLabel)}"
|
|
3609
|
+
class="${this.navLinkClasses}"
|
|
3610
|
+
href="${ifDefined_js.ifDefined(this.href)}"
|
|
3611
|
+
id="${ifDefined_js.ifDefined(this.id || void 0)}"
|
|
3612
|
+
title="${ifDefined_js.ifDefined(this.title || void 0)}"
|
|
3613
|
+
>${this.hasMenuContent ? this.menuContent : this.label}</a
|
|
3614
|
+
>`;
|
|
3615
|
+
}
|
|
3616
|
+
renderDropdown() {
|
|
3617
|
+
return lit.html`<div
|
|
3618
|
+
class="${this.wrapperClasses}"
|
|
3619
|
+
id="${ifDefined_js.ifDefined(this.id || void 0)}"
|
|
3620
|
+
title="${ifDefined_js.ifDefined(this.title || void 0)}"
|
|
3621
|
+
>
|
|
3622
|
+
<button
|
|
3623
|
+
aria-expanded="${this.open ? "true" : "false"}"
|
|
3624
|
+
aria-haspopup="true"
|
|
3625
|
+
aria-label="${ifDefined_js.ifDefined(this.forwardedAriaLabel ?? this.label)}"
|
|
3626
|
+
class="${this.navLinkClasses}"
|
|
3627
|
+
data-sp-nav-item-trigger
|
|
3628
|
+
type="button"
|
|
3629
|
+
@click="${() => this.toggle()}"
|
|
3630
|
+
>
|
|
3631
|
+
${this.hasTriggerContent ? this.triggerContent : this.label}
|
|
3632
|
+
</button>
|
|
3633
|
+
<div class="${this.menuClasses}" data-sp-nav-item-menu>
|
|
3634
|
+
${this.hasMenuContent ? this.menuContent : lit.nothing}
|
|
3635
|
+
</div>
|
|
3636
|
+
</div>`;
|
|
3637
|
+
}
|
|
3638
|
+
render() {
|
|
3639
|
+
return this.dropdown ? this.renderDropdown() : this.renderLink();
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
SpectreNavItemElement.properties = {
|
|
3643
|
+
dropdown: { type: Boolean, reflect: true },
|
|
3644
|
+
href: { type: String },
|
|
3645
|
+
label: { type: String },
|
|
3646
|
+
open: { type: Boolean, reflect: true },
|
|
3647
|
+
placement: { type: String, reflect: true }
|
|
3648
|
+
};
|
|
3649
|
+
function defineSpectreNavItem(tagName = "sp-nav-item") {
|
|
3650
|
+
const existingElement = customElements.get(tagName);
|
|
3651
|
+
if (existingElement) {
|
|
3652
|
+
return existingElement;
|
|
3653
|
+
}
|
|
3654
|
+
customElements.define(tagName, SpectreNavItemElement);
|
|
3655
|
+
return SpectreNavItemElement;
|
|
3656
|
+
}
|
|
3455
3657
|
var SpectreSidebarElement = class extends SpectreProjectableElement {
|
|
3456
3658
|
constructor() {
|
|
3457
3659
|
super(...arguments);
|
|
@@ -4012,6 +4214,7 @@ function defineSpectreComponents() {
|
|
|
4012
4214
|
defineSpectreFooter();
|
|
4013
4215
|
defineSpectreModal();
|
|
4014
4216
|
defineSpectreNav();
|
|
4217
|
+
defineSpectreNavItem();
|
|
4015
4218
|
defineSpectreSidebar();
|
|
4016
4219
|
defineSpectreToast();
|
|
4017
4220
|
defineSpectreTooltip();
|
|
@@ -4034,6 +4237,7 @@ exports.SpectreInputElement = SpectreInputElement;
|
|
|
4034
4237
|
exports.SpectreLabelElement = SpectreLabelElement;
|
|
4035
4238
|
exports.SpectreModalElement = SpectreModalElement;
|
|
4036
4239
|
exports.SpectreNavElement = SpectreNavElement;
|
|
4240
|
+
exports.SpectreNavItemElement = SpectreNavItemElement;
|
|
4037
4241
|
exports.SpectrePricingCardElement = SpectrePricingCardElement;
|
|
4038
4242
|
exports.SpectreRadioElement = SpectreRadioElement;
|
|
4039
4243
|
exports.SpectreRatingElement = SpectreRatingElement;
|
|
@@ -4065,6 +4269,7 @@ exports.defineSpectreInput = defineSpectreInput;
|
|
|
4065
4269
|
exports.defineSpectreLabel = defineSpectreLabel;
|
|
4066
4270
|
exports.defineSpectreModal = defineSpectreModal;
|
|
4067
4271
|
exports.defineSpectreNav = defineSpectreNav;
|
|
4272
|
+
exports.defineSpectreNavItem = defineSpectreNavItem;
|
|
4068
4273
|
exports.defineSpectrePricingCard = defineSpectrePricingCard;
|
|
4069
4274
|
exports.defineSpectreRadio = defineSpectreRadio;
|
|
4070
4275
|
exports.defineSpectreRating = defineSpectreRating;
|