@waggylabs/yumekit 0.4.1-beta.78 → 0.4.1-beta.79
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/CHANGELOG.md +8 -0
- package/dist/components/y-appbar/y-appbar.d.ts +6 -0
- package/dist/components/y-appbar/y-appbar.stories.d.ts +7 -2
- package/dist/components/y-appbar.js +123 -14
- package/dist/components/y-button/y-button.d.ts +10 -1
- package/dist/components/y-button/y-button.stories.d.ts +5 -0
- package/dist/components/y-button.js +73 -11
- package/dist/components/y-date.js +73 -11
- package/dist/components/y-datepicker.js +73 -11
- package/dist/components/y-menu.js +25 -2
- package/dist/components/y-panel.js +8 -0
- package/dist/components/y-panelbar/y-panelbar.stories.d.ts +7 -2
- package/dist/index.js +131 -14
- package/dist/yumekit.min.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,14 @@ Delete any empty sections before publishing.
|
|
|
31
31
|
<!-- ### Security -->
|
|
32
32
|
<!-- Vulnerability patches or hardening changes -->
|
|
33
33
|
|
|
34
|
+
## [Unreleased]
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `href`, `target`, and `rel` attributes on `y-button`. When `href` is set the internal element switches from `<button>` to `<a>`, preserving all visual styles and size/color/style-type variants. Disabled state is handled via `aria-disabled` and `pointer-events: none` since `<a>` has no native disabled.
|
|
39
|
+
- `navigate` custom event on `y-appbar`, `y-panel`, and `y-menu`. Fires before any navigation when an item with an `href` / `url` is clicked. The event is cancelable (`e.preventDefault()`) and carries `event.detail.href` — React Router and other SPA routers can intercept it without any framework-specific glue.
|
|
40
|
+
- `history` attribute on `y-appbar` and `y-menu` (already present on `y-panel`). When omitted (default), navigation uses `history.pushState` + a synthetic `popstate` event so all `popstate`-based routers (React Router `BrowserRouter`, Vue Router, etc.) respond automatically. Set `history="false"` to opt back in to full-page `window.location.href` navigation.
|
|
41
|
+
|
|
34
42
|
## [0.4.2] - 2026-04-11
|
|
35
43
|
|
|
36
44
|
### Changed
|
|
@@ -34,6 +34,12 @@ export class YumeAppbar extends HTMLElement {
|
|
|
34
34
|
set size(val: string);
|
|
35
35
|
/** Size variant: "small" | "medium" | "large" (default "medium"). */
|
|
36
36
|
get size(): string;
|
|
37
|
+
set history(val: string);
|
|
38
|
+
/**
|
|
39
|
+
* Navigation mode: omit for pushState (SPA-friendly), set to "false" for full-page navigation.
|
|
40
|
+
* Regardless of this setting, a cancelable "navigate" event is always dispatched first.
|
|
41
|
+
*/
|
|
42
|
+
get history(): string;
|
|
37
43
|
set sticky(val: string | false);
|
|
38
44
|
/** Sticky position: "start" | "end" | false. */
|
|
39
45
|
get sticky(): string | false;
|
|
@@ -79,7 +79,12 @@ export namespace Sizes {
|
|
|
79
79
|
export function render_2(): string;
|
|
80
80
|
export { render_2 as render };
|
|
81
81
|
}
|
|
82
|
-
export namespace
|
|
83
|
-
export
|
|
82
|
+
export namespace NavigateEvent {
|
|
83
|
+
export let name: string;
|
|
84
|
+
export function render_3(): HTMLDivElement;
|
|
84
85
|
export { render_3 as render };
|
|
85
86
|
}
|
|
87
|
+
export namespace WithFooter {
|
|
88
|
+
export function render_4(): string;
|
|
89
|
+
export { render_4 as render };
|
|
90
|
+
}
|
|
@@ -8,6 +8,7 @@ class YumeButton extends HTMLElement {
|
|
|
8
8
|
"disabled", "name", "value", "autofocus", "form", "formaction",
|
|
9
9
|
"formenctype", "formmethod", "formnovalidate", "formtarget",
|
|
10
10
|
"aria-label", "aria-pressed", "aria-hidden",
|
|
11
|
+
"href", "target", "rel",
|
|
11
12
|
];
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -49,6 +50,27 @@ class YumeButton extends HTMLElement {
|
|
|
49
50
|
// Getters / Setters
|
|
50
51
|
// -------------------------------------------------------------------------
|
|
51
52
|
|
|
53
|
+
/** URL to navigate to. When set, the internal element renders as an <a> instead of <button>. */
|
|
54
|
+
get href() { return this.getAttribute("href"); }
|
|
55
|
+
set href(val) {
|
|
56
|
+
if (val != null) this.setAttribute("href", val);
|
|
57
|
+
else this.removeAttribute("href");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Anchor target (e.g. "_blank"). Only applies when href is set. */
|
|
61
|
+
get target() { return this.getAttribute("target"); }
|
|
62
|
+
set target(val) {
|
|
63
|
+
if (val != null) this.setAttribute("target", val);
|
|
64
|
+
else this.removeAttribute("target");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Anchor rel attribute (e.g. "noopener noreferrer"). Only applies when href is set. */
|
|
68
|
+
get rel() { return this.getAttribute("rel"); }
|
|
69
|
+
set rel(val) {
|
|
70
|
+
if (val != null) this.setAttribute("rel", val);
|
|
71
|
+
else this.removeAttribute("rel");
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
/** Color theme for the button (default "base"). */
|
|
53
75
|
get color() { return this.getAttribute("color") || "base"; }
|
|
54
76
|
set color(val) { this.setAttribute("color", val); }
|
|
@@ -287,9 +309,15 @@ class YumeButton extends HTMLElement {
|
|
|
287
309
|
line-height: 1;
|
|
288
310
|
}
|
|
289
311
|
|
|
290
|
-
.button
|
|
312
|
+
.button {
|
|
313
|
+
text-decoration: none;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.button:disabled,
|
|
317
|
+
.button[aria-disabled="true"] {
|
|
291
318
|
opacity: 0.5;
|
|
292
319
|
cursor: not-allowed;
|
|
320
|
+
pointer-events: none;
|
|
293
321
|
}
|
|
294
322
|
|
|
295
323
|
.button:hover:not(:disabled),
|
|
@@ -419,23 +447,49 @@ class YumeButton extends HTMLElement {
|
|
|
419
447
|
}
|
|
420
448
|
|
|
421
449
|
_render() {
|
|
450
|
+
const needsAnchor = this.hasAttribute("href");
|
|
451
|
+
const isAnchor = this.button?.tagName === "A";
|
|
452
|
+
|
|
453
|
+
if (this.button && needsAnchor !== isAnchor) {
|
|
454
|
+
this.button.remove();
|
|
455
|
+
this.button = null;
|
|
456
|
+
}
|
|
457
|
+
|
|
422
458
|
if (!this.button) {
|
|
423
|
-
this.button =
|
|
459
|
+
this.button = needsAnchor
|
|
460
|
+
? document.createElement("a")
|
|
461
|
+
: document.createElement("button");
|
|
424
462
|
this.button.classList.add("button");
|
|
425
|
-
this.button.setAttribute("role", "button");
|
|
426
|
-
this.button.setAttribute("tabindex", "0");
|
|
427
463
|
this.button.setAttribute("part", "button");
|
|
464
|
+
if (!needsAnchor) {
|
|
465
|
+
this.button.setAttribute("role", "button");
|
|
466
|
+
this.button.setAttribute("tabindex", "0");
|
|
467
|
+
}
|
|
428
468
|
this.shadowRoot.appendChild(this.button);
|
|
429
469
|
}
|
|
430
470
|
|
|
431
471
|
this._updateButtonAttributes();
|
|
432
472
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
473
|
+
const disabled = this.hasAttribute("disabled");
|
|
474
|
+
if (needsAnchor) {
|
|
475
|
+
// <a> has no native disabled — manage via aria and href removal
|
|
476
|
+
if (disabled) {
|
|
477
|
+
this.button.removeAttribute("href");
|
|
478
|
+
this.button.setAttribute("aria-disabled", "true");
|
|
479
|
+
this.button.setAttribute("tabindex", "-1");
|
|
480
|
+
} else {
|
|
481
|
+
this.button.setAttribute("href", this.getAttribute("href"));
|
|
482
|
+
this.button.setAttribute("aria-disabled", "false");
|
|
483
|
+
this.button.removeAttribute("tabindex");
|
|
484
|
+
}
|
|
436
485
|
} else {
|
|
437
|
-
|
|
438
|
-
|
|
486
|
+
if (disabled) {
|
|
487
|
+
this.button.setAttribute("disabled", "");
|
|
488
|
+
this.button.setAttribute("aria-disabled", "true");
|
|
489
|
+
} else {
|
|
490
|
+
this.button.removeAttribute("disabled");
|
|
491
|
+
this.button.setAttribute("aria-disabled", "false");
|
|
492
|
+
}
|
|
439
493
|
}
|
|
440
494
|
|
|
441
495
|
this.button.innerHTML = `
|
|
@@ -450,9 +504,17 @@ class YumeButton extends HTMLElement {
|
|
|
450
504
|
}
|
|
451
505
|
|
|
452
506
|
_updateButtonAttributes() {
|
|
453
|
-
const
|
|
507
|
+
const isAnchor = this.button?.tagName === "A";
|
|
508
|
+
// These are only meaningful on <button>
|
|
509
|
+
const buttonOnlyAttrs = new Set(["type", "disabled", "name", "value", "autofocus", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget"]);
|
|
510
|
+
// These are only meaningful on <a>; href is managed separately in _render
|
|
511
|
+
const anchorOnlyAttrs = new Set(["href", "target", "rel"]);
|
|
512
|
+
|
|
513
|
+
YumeButton.observedAttributes.forEach((attr) => {
|
|
514
|
+
if (isAnchor && buttonOnlyAttrs.has(attr)) return;
|
|
515
|
+
if (isAnchor && attr === "href") return; // handled in _render (disabled-aware)
|
|
516
|
+
if (!isAnchor && anchorOnlyAttrs.has(attr)) return;
|
|
454
517
|
|
|
455
|
-
attributes.forEach((attr) => {
|
|
456
518
|
if (this.hasAttribute(attr)) {
|
|
457
519
|
this.button.setAttribute(attr, this.getAttribute(attr));
|
|
458
520
|
} else {
|
|
@@ -732,7 +794,7 @@ var menu = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill
|
|
|
732
794
|
|
|
733
795
|
class YumeMenu extends HTMLElement {
|
|
734
796
|
static get observedAttributes() {
|
|
735
|
-
return ["items", "anchor", "visible", "direction", "size"];
|
|
797
|
+
return ["items", "anchor", "visible", "direction", "size", "history"];
|
|
736
798
|
}
|
|
737
799
|
|
|
738
800
|
// -------------------------------------------------------------------------
|
|
@@ -810,6 +872,16 @@ class YumeMenu extends HTMLElement {
|
|
|
810
872
|
this.setAttribute("items", Array.isArray(val) ? JSON.stringify(val) : (val ?? "[]"));
|
|
811
873
|
}
|
|
812
874
|
|
|
875
|
+
/**
|
|
876
|
+
* Navigation mode: omit for pushState (SPA-friendly), set to "false" for full-page navigation.
|
|
877
|
+
* Regardless of this setting, a cancelable "navigate" event is always dispatched first.
|
|
878
|
+
*/
|
|
879
|
+
get history() { return this.getAttribute("history"); }
|
|
880
|
+
set history(val) {
|
|
881
|
+
if (val != null) this.setAttribute("history", val);
|
|
882
|
+
else this.removeAttribute("history");
|
|
883
|
+
}
|
|
884
|
+
|
|
813
885
|
/** Size: "small" | "medium" | "large" (default "medium"). */
|
|
814
886
|
get size() {
|
|
815
887
|
const sz = this.getAttribute("size");
|
|
@@ -962,7 +1034,20 @@ class YumeMenu extends HTMLElement {
|
|
|
962
1034
|
|
|
963
1035
|
if (item.url) {
|
|
964
1036
|
li.addEventListener("click", () => {
|
|
965
|
-
|
|
1037
|
+
const event = new CustomEvent("navigate", {
|
|
1038
|
+
bubbles: true,
|
|
1039
|
+
composed: true,
|
|
1040
|
+
cancelable: true,
|
|
1041
|
+
detail: { href: item.url },
|
|
1042
|
+
});
|
|
1043
|
+
const cancelled = !this.dispatchEvent(event);
|
|
1044
|
+
if (cancelled) return;
|
|
1045
|
+
if (this.getAttribute("history") !== "false") {
|
|
1046
|
+
history.pushState({}, "", item.url);
|
|
1047
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state: {} }));
|
|
1048
|
+
} else {
|
|
1049
|
+
window.location.href = item.url;
|
|
1050
|
+
}
|
|
966
1051
|
});
|
|
967
1052
|
}
|
|
968
1053
|
|
|
@@ -1143,6 +1228,7 @@ class YumeAppbar extends HTMLElement {
|
|
|
1143
1228
|
"menu-direction",
|
|
1144
1229
|
"sticky",
|
|
1145
1230
|
"mobile-breakpoint",
|
|
1231
|
+
"history",
|
|
1146
1232
|
];
|
|
1147
1233
|
}
|
|
1148
1234
|
|
|
@@ -1229,6 +1315,16 @@ class YumeAppbar extends HTMLElement {
|
|
|
1229
1315
|
get size() { return this.getAttribute("size") || "medium"; }
|
|
1230
1316
|
set size(val) { this.setAttribute("size", val); }
|
|
1231
1317
|
|
|
1318
|
+
/**
|
|
1319
|
+
* Navigation mode: omit for pushState (SPA-friendly), set to "false" for full-page navigation.
|
|
1320
|
+
* Regardless of this setting, a cancelable "navigate" event is always dispatched first.
|
|
1321
|
+
*/
|
|
1322
|
+
get history() { return this.getAttribute("history"); }
|
|
1323
|
+
set history(val) {
|
|
1324
|
+
if (val != null) this.setAttribute("history", val);
|
|
1325
|
+
else this.removeAttribute("history");
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1232
1328
|
/** Sticky position: "start" | "end" | false. */
|
|
1233
1329
|
get sticky() {
|
|
1234
1330
|
const val = this.getAttribute("sticky");
|
|
@@ -1346,7 +1442,20 @@ class YumeAppbar extends HTMLElement {
|
|
|
1346
1442
|
|
|
1347
1443
|
if (item.href && !hasChildren) {
|
|
1348
1444
|
btn.addEventListener("click", () => {
|
|
1349
|
-
|
|
1445
|
+
const event = new CustomEvent("navigate", {
|
|
1446
|
+
bubbles: true,
|
|
1447
|
+
composed: true,
|
|
1448
|
+
cancelable: true,
|
|
1449
|
+
detail: { href: item.href },
|
|
1450
|
+
});
|
|
1451
|
+
const cancelled = !this.dispatchEvent(event);
|
|
1452
|
+
if (cancelled) return;
|
|
1453
|
+
if (this.getAttribute("history") !== "false") {
|
|
1454
|
+
history.pushState({}, "", item.href);
|
|
1455
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state: {} }));
|
|
1456
|
+
} else {
|
|
1457
|
+
window.location.href = item.href;
|
|
1458
|
+
}
|
|
1350
1459
|
});
|
|
1351
1460
|
}
|
|
1352
1461
|
|
|
@@ -3,6 +3,15 @@ export class YumeButton extends HTMLElement {
|
|
|
3
3
|
selectedValues: Set<any>;
|
|
4
4
|
connectedCallback(): void;
|
|
5
5
|
attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
|
|
6
|
+
set href(val: string);
|
|
7
|
+
/** URL to navigate to. When set, the internal element renders as an <a> instead of <button>. */
|
|
8
|
+
get href(): string;
|
|
9
|
+
set target(val: string);
|
|
10
|
+
/** Anchor target (e.g. "_blank"). Only applies when href is set. */
|
|
11
|
+
get target(): string;
|
|
12
|
+
set rel(val: string);
|
|
13
|
+
/** Anchor rel attribute (e.g. "noopener noreferrer"). Only applies when href is set. */
|
|
14
|
+
get rel(): string;
|
|
6
15
|
set color(val: string);
|
|
7
16
|
/** Color theme for the button (default "base"). */
|
|
8
17
|
get color(): string;
|
|
@@ -50,7 +59,7 @@ export class YumeButton extends HTMLElement {
|
|
|
50
59
|
_manageSlotVisibility(slotName: any, selector: any): void;
|
|
51
60
|
_proxyNativeOnClick(): void;
|
|
52
61
|
_render(): void;
|
|
53
|
-
button:
|
|
62
|
+
button: any;
|
|
54
63
|
_updateButtonAttributes(): void;
|
|
55
64
|
_updateStyles(): void;
|
|
56
65
|
}
|
|
@@ -7,6 +7,7 @@ class YumeButton extends HTMLElement {
|
|
|
7
7
|
"disabled", "name", "value", "autofocus", "form", "formaction",
|
|
8
8
|
"formenctype", "formmethod", "formnovalidate", "formtarget",
|
|
9
9
|
"aria-label", "aria-pressed", "aria-hidden",
|
|
10
|
+
"href", "target", "rel",
|
|
10
11
|
];
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -48,6 +49,27 @@ class YumeButton extends HTMLElement {
|
|
|
48
49
|
// Getters / Setters
|
|
49
50
|
// -------------------------------------------------------------------------
|
|
50
51
|
|
|
52
|
+
/** URL to navigate to. When set, the internal element renders as an <a> instead of <button>. */
|
|
53
|
+
get href() { return this.getAttribute("href"); }
|
|
54
|
+
set href(val) {
|
|
55
|
+
if (val != null) this.setAttribute("href", val);
|
|
56
|
+
else this.removeAttribute("href");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Anchor target (e.g. "_blank"). Only applies when href is set. */
|
|
60
|
+
get target() { return this.getAttribute("target"); }
|
|
61
|
+
set target(val) {
|
|
62
|
+
if (val != null) this.setAttribute("target", val);
|
|
63
|
+
else this.removeAttribute("target");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Anchor rel attribute (e.g. "noopener noreferrer"). Only applies when href is set. */
|
|
67
|
+
get rel() { return this.getAttribute("rel"); }
|
|
68
|
+
set rel(val) {
|
|
69
|
+
if (val != null) this.setAttribute("rel", val);
|
|
70
|
+
else this.removeAttribute("rel");
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
/** Color theme for the button (default "base"). */
|
|
52
74
|
get color() { return this.getAttribute("color") || "base"; }
|
|
53
75
|
set color(val) { this.setAttribute("color", val); }
|
|
@@ -286,9 +308,15 @@ class YumeButton extends HTMLElement {
|
|
|
286
308
|
line-height: 1;
|
|
287
309
|
}
|
|
288
310
|
|
|
289
|
-
.button
|
|
311
|
+
.button {
|
|
312
|
+
text-decoration: none;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.button:disabled,
|
|
316
|
+
.button[aria-disabled="true"] {
|
|
290
317
|
opacity: 0.5;
|
|
291
318
|
cursor: not-allowed;
|
|
319
|
+
pointer-events: none;
|
|
292
320
|
}
|
|
293
321
|
|
|
294
322
|
.button:hover:not(:disabled),
|
|
@@ -418,23 +446,49 @@ class YumeButton extends HTMLElement {
|
|
|
418
446
|
}
|
|
419
447
|
|
|
420
448
|
_render() {
|
|
449
|
+
const needsAnchor = this.hasAttribute("href");
|
|
450
|
+
const isAnchor = this.button?.tagName === "A";
|
|
451
|
+
|
|
452
|
+
if (this.button && needsAnchor !== isAnchor) {
|
|
453
|
+
this.button.remove();
|
|
454
|
+
this.button = null;
|
|
455
|
+
}
|
|
456
|
+
|
|
421
457
|
if (!this.button) {
|
|
422
|
-
this.button =
|
|
458
|
+
this.button = needsAnchor
|
|
459
|
+
? document.createElement("a")
|
|
460
|
+
: document.createElement("button");
|
|
423
461
|
this.button.classList.add("button");
|
|
424
|
-
this.button.setAttribute("role", "button");
|
|
425
|
-
this.button.setAttribute("tabindex", "0");
|
|
426
462
|
this.button.setAttribute("part", "button");
|
|
463
|
+
if (!needsAnchor) {
|
|
464
|
+
this.button.setAttribute("role", "button");
|
|
465
|
+
this.button.setAttribute("tabindex", "0");
|
|
466
|
+
}
|
|
427
467
|
this.shadowRoot.appendChild(this.button);
|
|
428
468
|
}
|
|
429
469
|
|
|
430
470
|
this._updateButtonAttributes();
|
|
431
471
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
472
|
+
const disabled = this.hasAttribute("disabled");
|
|
473
|
+
if (needsAnchor) {
|
|
474
|
+
// <a> has no native disabled — manage via aria and href removal
|
|
475
|
+
if (disabled) {
|
|
476
|
+
this.button.removeAttribute("href");
|
|
477
|
+
this.button.setAttribute("aria-disabled", "true");
|
|
478
|
+
this.button.setAttribute("tabindex", "-1");
|
|
479
|
+
} else {
|
|
480
|
+
this.button.setAttribute("href", this.getAttribute("href"));
|
|
481
|
+
this.button.setAttribute("aria-disabled", "false");
|
|
482
|
+
this.button.removeAttribute("tabindex");
|
|
483
|
+
}
|
|
435
484
|
} else {
|
|
436
|
-
|
|
437
|
-
|
|
485
|
+
if (disabled) {
|
|
486
|
+
this.button.setAttribute("disabled", "");
|
|
487
|
+
this.button.setAttribute("aria-disabled", "true");
|
|
488
|
+
} else {
|
|
489
|
+
this.button.removeAttribute("disabled");
|
|
490
|
+
this.button.setAttribute("aria-disabled", "false");
|
|
491
|
+
}
|
|
438
492
|
}
|
|
439
493
|
|
|
440
494
|
this.button.innerHTML = `
|
|
@@ -449,9 +503,17 @@ class YumeButton extends HTMLElement {
|
|
|
449
503
|
}
|
|
450
504
|
|
|
451
505
|
_updateButtonAttributes() {
|
|
452
|
-
const
|
|
506
|
+
const isAnchor = this.button?.tagName === "A";
|
|
507
|
+
// These are only meaningful on <button>
|
|
508
|
+
const buttonOnlyAttrs = new Set(["type", "disabled", "name", "value", "autofocus", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget"]);
|
|
509
|
+
// These are only meaningful on <a>; href is managed separately in _render
|
|
510
|
+
const anchorOnlyAttrs = new Set(["href", "target", "rel"]);
|
|
511
|
+
|
|
512
|
+
YumeButton.observedAttributes.forEach((attr) => {
|
|
513
|
+
if (isAnchor && buttonOnlyAttrs.has(attr)) return;
|
|
514
|
+
if (isAnchor && attr === "href") return; // handled in _render (disabled-aware)
|
|
515
|
+
if (!isAnchor && anchorOnlyAttrs.has(attr)) return;
|
|
453
516
|
|
|
454
|
-
attributes.forEach((attr) => {
|
|
455
517
|
if (this.hasAttribute(attr)) {
|
|
456
518
|
this.button.setAttribute(attr, this.getAttribute(attr));
|
|
457
519
|
} else {
|
|
@@ -8,6 +8,7 @@ class YumeButton extends HTMLElement {
|
|
|
8
8
|
"disabled", "name", "value", "autofocus", "form", "formaction",
|
|
9
9
|
"formenctype", "formmethod", "formnovalidate", "formtarget",
|
|
10
10
|
"aria-label", "aria-pressed", "aria-hidden",
|
|
11
|
+
"href", "target", "rel",
|
|
11
12
|
];
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -49,6 +50,27 @@ class YumeButton extends HTMLElement {
|
|
|
49
50
|
// Getters / Setters
|
|
50
51
|
// -------------------------------------------------------------------------
|
|
51
52
|
|
|
53
|
+
/** URL to navigate to. When set, the internal element renders as an <a> instead of <button>. */
|
|
54
|
+
get href() { return this.getAttribute("href"); }
|
|
55
|
+
set href(val) {
|
|
56
|
+
if (val != null) this.setAttribute("href", val);
|
|
57
|
+
else this.removeAttribute("href");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Anchor target (e.g. "_blank"). Only applies when href is set. */
|
|
61
|
+
get target() { return this.getAttribute("target"); }
|
|
62
|
+
set target(val) {
|
|
63
|
+
if (val != null) this.setAttribute("target", val);
|
|
64
|
+
else this.removeAttribute("target");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Anchor rel attribute (e.g. "noopener noreferrer"). Only applies when href is set. */
|
|
68
|
+
get rel() { return this.getAttribute("rel"); }
|
|
69
|
+
set rel(val) {
|
|
70
|
+
if (val != null) this.setAttribute("rel", val);
|
|
71
|
+
else this.removeAttribute("rel");
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
/** Color theme for the button (default "base"). */
|
|
53
75
|
get color() { return this.getAttribute("color") || "base"; }
|
|
54
76
|
set color(val) { this.setAttribute("color", val); }
|
|
@@ -287,9 +309,15 @@ class YumeButton extends HTMLElement {
|
|
|
287
309
|
line-height: 1;
|
|
288
310
|
}
|
|
289
311
|
|
|
290
|
-
.button
|
|
312
|
+
.button {
|
|
313
|
+
text-decoration: none;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.button:disabled,
|
|
317
|
+
.button[aria-disabled="true"] {
|
|
291
318
|
opacity: 0.5;
|
|
292
319
|
cursor: not-allowed;
|
|
320
|
+
pointer-events: none;
|
|
293
321
|
}
|
|
294
322
|
|
|
295
323
|
.button:hover:not(:disabled),
|
|
@@ -419,23 +447,49 @@ class YumeButton extends HTMLElement {
|
|
|
419
447
|
}
|
|
420
448
|
|
|
421
449
|
_render() {
|
|
450
|
+
const needsAnchor = this.hasAttribute("href");
|
|
451
|
+
const isAnchor = this.button?.tagName === "A";
|
|
452
|
+
|
|
453
|
+
if (this.button && needsAnchor !== isAnchor) {
|
|
454
|
+
this.button.remove();
|
|
455
|
+
this.button = null;
|
|
456
|
+
}
|
|
457
|
+
|
|
422
458
|
if (!this.button) {
|
|
423
|
-
this.button =
|
|
459
|
+
this.button = needsAnchor
|
|
460
|
+
? document.createElement("a")
|
|
461
|
+
: document.createElement("button");
|
|
424
462
|
this.button.classList.add("button");
|
|
425
|
-
this.button.setAttribute("role", "button");
|
|
426
|
-
this.button.setAttribute("tabindex", "0");
|
|
427
463
|
this.button.setAttribute("part", "button");
|
|
464
|
+
if (!needsAnchor) {
|
|
465
|
+
this.button.setAttribute("role", "button");
|
|
466
|
+
this.button.setAttribute("tabindex", "0");
|
|
467
|
+
}
|
|
428
468
|
this.shadowRoot.appendChild(this.button);
|
|
429
469
|
}
|
|
430
470
|
|
|
431
471
|
this._updateButtonAttributes();
|
|
432
472
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
473
|
+
const disabled = this.hasAttribute("disabled");
|
|
474
|
+
if (needsAnchor) {
|
|
475
|
+
// <a> has no native disabled — manage via aria and href removal
|
|
476
|
+
if (disabled) {
|
|
477
|
+
this.button.removeAttribute("href");
|
|
478
|
+
this.button.setAttribute("aria-disabled", "true");
|
|
479
|
+
this.button.setAttribute("tabindex", "-1");
|
|
480
|
+
} else {
|
|
481
|
+
this.button.setAttribute("href", this.getAttribute("href"));
|
|
482
|
+
this.button.setAttribute("aria-disabled", "false");
|
|
483
|
+
this.button.removeAttribute("tabindex");
|
|
484
|
+
}
|
|
436
485
|
} else {
|
|
437
|
-
|
|
438
|
-
|
|
486
|
+
if (disabled) {
|
|
487
|
+
this.button.setAttribute("disabled", "");
|
|
488
|
+
this.button.setAttribute("aria-disabled", "true");
|
|
489
|
+
} else {
|
|
490
|
+
this.button.removeAttribute("disabled");
|
|
491
|
+
this.button.setAttribute("aria-disabled", "false");
|
|
492
|
+
}
|
|
439
493
|
}
|
|
440
494
|
|
|
441
495
|
this.button.innerHTML = `
|
|
@@ -450,9 +504,17 @@ class YumeButton extends HTMLElement {
|
|
|
450
504
|
}
|
|
451
505
|
|
|
452
506
|
_updateButtonAttributes() {
|
|
453
|
-
const
|
|
507
|
+
const isAnchor = this.button?.tagName === "A";
|
|
508
|
+
// These are only meaningful on <button>
|
|
509
|
+
const buttonOnlyAttrs = new Set(["type", "disabled", "name", "value", "autofocus", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget"]);
|
|
510
|
+
// These are only meaningful on <a>; href is managed separately in _render
|
|
511
|
+
const anchorOnlyAttrs = new Set(["href", "target", "rel"]);
|
|
512
|
+
|
|
513
|
+
YumeButton.observedAttributes.forEach((attr) => {
|
|
514
|
+
if (isAnchor && buttonOnlyAttrs.has(attr)) return;
|
|
515
|
+
if (isAnchor && attr === "href") return; // handled in _render (disabled-aware)
|
|
516
|
+
if (!isAnchor && anchorOnlyAttrs.has(attr)) return;
|
|
454
517
|
|
|
455
|
-
attributes.forEach((attr) => {
|
|
456
518
|
if (this.hasAttribute(attr)) {
|
|
457
519
|
this.button.setAttribute(attr, this.getAttribute(attr));
|
|
458
520
|
} else {
|