Add transaction details and transaction history features

Implemented transaction details view for Solana transactions, including sender, receiver, value, fee, slot, and status.
Added transaction history fetching, pagination, and filtering (all/sent/received).
Improved UI for transaction navigation and error handling.
This commit is contained in:
void-57 2026-01-13 22:14:40 +05:30
parent dfa3337226
commit 5666fe5d34
3 changed files with 3882 additions and 1361 deletions

View File

@ -28,7 +28,7 @@ body {
background-color: rgba(var(--foreground-color), 1);
}
body[data-theme=dark] {
body[data-theme="dark"] {
--accent-color: #92a2ff;
--accent-color-rgb: 160, 182, 255;
--secondary-color: #d60739;
@ -39,7 +39,7 @@ body[data-theme=dark] {
--green: #00e676;
--yellow: rgb(255, 213, 5);
}
body[data-theme=dark] ::-webkit-calendar-picker-indicator {
body[data-theme="dark"] ::-webkit-calendar-picker-indicator {
filter: invert(1);
}
@ -90,7 +90,7 @@ a:any-link:focus-visible {
outline: rgba(var(--text-color), 1) 0.1rem solid;
}
input[type=datetime-local] {
input[type="datetime-local"] {
width: 100%;
padding: 0.8rem 0.6rem;
border: none;
@ -101,7 +101,7 @@ input[type=datetime-local] {
color: inherit;
background-color: rgba(var(--text-color), 0.06);
}
input[type=datetime-local]:focus {
input[type="datetime-local"]:focus {
outline: none;
box-shadow: 0 0 0 0.1rem var(--accent-color);
}
@ -604,7 +604,11 @@ ul {
position: absolute;
border-radius: 50%;
transform: scale(0);
background: radial-gradient(circle, rgba(var(--text-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%);
background: radial-gradient(
circle,
rgba(var(--text-color), 0.3) 0%,
rgba(0, 0, 0, 0) 50%
);
pointer-events: none;
}
@ -699,17 +703,17 @@ ul {
justify-self: flex-start;
}
ul[type=circle],
menu[type=circle] {
ul[type="circle"],
menu[type="circle"] {
padding: 1.5rem 2.5rem;
list-style: circle;
}
ul[type=circle] li,
menu[type=circle] li {
ul[type="circle"] li,
menu[type="circle"] li {
margin-bottom: 1rem;
}
ul[type=circle] li:last-of-type,
menu[type=circle] li:last-of-type {
ul[type="circle"] li:last-of-type,
menu[type="circle"] li:last-of-type {
margin-bottom: 0;
}
ul,
@ -773,13 +777,13 @@ menu {
#meta_mask_status_button .icon-wrapper > * {
grid-area: 1/1;
}
#meta_mask_status_button[data-status=connected] {
#meta_mask_status_button[data-status="connected"] {
pointer-events: none;
}
#meta_mask_status_button[data-status=connected] .icon-wrapper::after {
#meta_mask_status_button[data-status="connected"] .icon-wrapper::after {
background-color: var(--green);
}
#meta_mask_status_button[data-status=disconnected] .icon-wrapper::after {
#meta_mask_status_button[data-status="disconnected"] .icon-wrapper::after {
background-color: var(--danger-color);
}
@ -847,7 +851,8 @@ main {
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.nav-item__title {
transition: opacity 0.2s, transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition: opacity 0.2s,
transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.nav-item--active {
color: var(--accent-color);
@ -870,17 +875,17 @@ main {
overflow: auto;
grid-area: pages;
}
#page_container[data-page=home] > :nth-child(2) {
#page_container[data-page="home"] > :nth-child(2) {
flex: 1;
}
#page_container[data-page=send] {
#page_container[data-page="send"] {
align-items: flex-start;
}
#page_container[data-page=send] > * {
#page_container[data-page="send"] > * {
padding: 1rem;
margin: 0 auto;
}
#page_container[data-page=create] {
#page_container[data-page="create"] {
margin: 0 auto;
padding: 4vw 1rem;
gap: 2rem;
@ -951,6 +956,19 @@ aside h4 {
padding-bottom: 0.5rem;
}
#address_transactions {
width: 100%;
max-width: 32rem;
}
.transaction {
width: 100%;
}
#transactions_list {
width: 100%;
}
#error_section {
display: grid;
height: 100%;
@ -978,6 +996,7 @@ aside h4 {
position: relative;
margin-bottom: 2rem;
}
.transaction__phase:not(:last-of-type)::after {
content: "";
position: absolute;
@ -1068,17 +1087,24 @@ aside h4 {
padding-bottom: 1rem;
border-bottom: solid thin rgba(var(--text-color), 0.3);
}
.create-buttons {
display: flex;
max-width: 400px;
gap: 1rem;
}
@media only screen and (max-width: 640px) {
.hide-on-small {
display: none;
}
#page_container[data-page=home] {
#page_container[data-page="home"] {
flex-direction: column;
}
#page_container[data-page=home] > :first-child {
#page_container[data-page="home"] > :first-child {
order: 1;
}
.create-buttons {
display: grid;
}
}
@media only screen and (min-width: 640px) {
sm-popup {
@ -1172,3 +1198,378 @@ aside h4 {
animation: none !important;
}
}
.tx-details-container {
max-width: 800px;
margin: 1rem auto;
padding: 1rem;
font-family: inherit;
}
/* Header styling */
.tx-header {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(var(--text-color), 0.1);
}
.tx-title {
font-size: 1.5rem;
font-weight: 600;
color: rgba(var(--text-color), 0.95);
margin: 0;
}
.tx-card {
background-color: rgba(var(--foreground-color), 1);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(var(--text-color), 0.1);
margin-top: 1rem;
}
.tx-status-header {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem;
background-color: rgba(var(--text-color), 0.03);
border-bottom: 1px solid rgba(var(--text-color), 0.1);
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.status-indicator.confirmed {
background-color: var(--color-success);
box-shadow: 0 0 0 4px rgba(var(--color-success-rgb), 0.2);
}
.status-indicator.pending {
background-color: var(--color-warning);
box-shadow: 0 0 0 4px rgba(var(--color-warning-rgb), 0.2);
}
.status-details {
flex-grow: 1;
}
.status-title {
font-size: 1.15rem;
font-weight: 600;
color: rgba(var(--text-color), 0.95);
margin: 0;
}
.status-subtext {
font-size: 0.85rem;
color: rgba(var(--text-color), 0.7);
margin: 0.25rem 0 0;
}
.tx-info-grid {
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.tx-address-section {
display: flex;
align-items: stretch;
gap: 1rem;
background: rgba(var(--text-color), 0.02);
padding: 1rem;
border-radius: 0.5rem;
}
.address-card {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.address-label {
font-size: 0.75rem;
font-weight: 500;
color: rgba(var(--text-color), 0.6);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.address-value {
font-family: "Roboto Mono", monospace;
font-size: 0.85rem;
color: rgba(var(--text-color), 0.9);
word-break: break-all;
background: rgba(var(--text-color), 0.05);
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
}
.tx-arrow {
font-size: 1.5rem;
color: rgba(var(--text-color), 0.4);
display: flex;
align-items: center;
padding: 0 0.5rem;
}
.tx-hash-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.section-label {
font-size: 0.75rem;
font-weight: 500;
color: rgba(var(--text-color), 0.6);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.hash-value {
background-color: rgba(var(--text-color), 0.05);
padding: 0.75rem;
border-radius: 0.25rem;
font-family: "Roboto Mono", monospace;
font-size: 0.85rem;
color: rgba(var(--text-color), 0.9);
word-break: break-all;
}
.tx-metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.metric-card {
background-color: rgba(var(--text-color), 0.03);
padding: 1rem;
border-radius: 0.5rem;
text-align: center;
border: 1px solid rgba(var(--text-color), 0.08);
transition: background-color 0.2s ease, transform 0.2s ease;
}
.metric-card:hover {
background-color: rgba(var(--text-color), 0.06);
transform: translateY(-2px);
}
.metric-label {
font-size: 0.75rem;
font-weight: 500;
color: rgba(var(--text-color), 0.6);
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.5px;
display: block;
}
.metric-value {
font-size: 0.95rem;
font-weight: 500;
color: rgba(var(--text-color), 0.95);
word-break: break-word;
}
.tx-actions {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 1.25rem;
border-top: 1px solid rgba(var(--text-color), 0.1);
background-color: rgba(var(--text-color), 0.03);
}
.tx-actions .button {
flex: 1;
}
@media (max-width: 768px) {
.tx-address-section {
flex-direction: column;
gap: 0.75rem;
align-items: stretch;
}
.tx-arrow {
transform: rotate(90deg);
margin: 0.5rem auto;
padding: 0;
}
.tx-metrics-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 576px) {
.tx-header {
padding-bottom: 0.75rem;
}
.tx-status-header {
padding: 1rem;
gap: 0.75rem;
}
.status-title {
font-size: 1.1rem;
}
.tx-info-grid {
padding: 1rem;
gap: 1.25rem;
}
.tx-address-section {
padding: 0.75rem;
}
.tx-metrics-grid {
grid-template-columns: 1fr;
}
.tx-actions {
flex-direction: column;
padding: 1rem;
}
}
/* Valuation toggle styles */
.valuation-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: rgba(var(--text-color), 0.8);
margin-top: 0.5rem;
margin-right: 0.5rem;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 3rem;
height: 1.5rem;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(var(--text-color), 0.2);
transition: 0.4s;
border-radius: 1.5rem;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 1.1rem;
width: 1.1rem;
left: 0.2rem;
bottom: 0.2rem;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--accent-color);
}
input:checked + .toggle-slider:before {
transform: translateX(1.5rem);
}
.transaction-controls {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
padding: 0.5rem;
background-color: rgba(var(--text-color), 0.03);
border-radius: 0.5rem;
border: 1px solid rgba(var(--text-color), 0.08);
}
.filter-control {
display: flex;
align-items: center;
gap: 0.5rem;
}
.filter-control label {
font-size: 0.9rem;
color: rgba(var(--text-color), 0.8);
font-weight: 500;
}
.filter-control select {
padding: 0.4rem 0.6rem;
border-radius: 0.4rem;
border: 1px solid rgba(var(--text-color), 0.1);
background-color: rgba(var(--foreground-color), 1);
color: rgba(var(--text-color), 0.9);
font-size: 0.9rem;
cursor: pointer;
outline: none;
}
.margin-left-auto + .margin-left-auto {
display: none !important;
}
.filter-control select:focus {
border-color: var(--accent-color);
}
/* Loading state for transaction details */
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
gap: 1rem;
}
.loading-text {
color: rgba(var(--text-color), 0.7);
font-size: 0.9rem;
}
#sol_balance_wrapper {
background-color: rgba(var(--text-color), 0.06);
padding: max(1rem, 1.5vw);
border-radius: 0.5rem;
width: 100%;
}
#sol_balance_wrapper li:not(:last-of-type) {
border-bottom: solid thin rgba(var(--text-color), 0.3);
padding-bottom: 0.5rem;
}

2336
index.html

File diff suppressed because it is too large Load Diff

View File

@ -28,8 +28,11 @@ const smChips = document.createElement("template");
get value() {
return this._value;
}
set value(t) {
this.setSelectedOption(t);
set value(val) {
this.setSelectedOption(val);
}
get isValid() {
return void 0 !== this._value;
}
scrollLeft() {
this.chipsWrapper.scrollBy({
@ -43,18 +46,18 @@ const smChips = document.createElement("template");
behavior: "smooth",
});
}
setSelectedOption(t) {
this._value !== t &&
((this._value = t),
this.assignedElements.forEach((e) => {
e.value == t
? (e.setAttribute("selected", ""),
e.scrollIntoView({
setSelectedOption(value) {
this._value !== value &&
((this._value = value),
this.assignedElements.forEach((elem) => {
elem.value == value
? (elem.setAttribute("selected", ""),
elem.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center",
}))
: e.removeAttribute("selected");
: elem.removeAttribute("selected");
}));
}
fireEvent() {
@ -68,54 +71,51 @@ const smChips = document.createElement("template");
}
connectedCallback() {
this.setAttribute("role", "listbox");
const t = this.shadowRoot.querySelector("slot");
t.addEventListener("slotchange", (e) => {
n.disconnect(),
i.disconnect(),
const slot = this.shadowRoot.querySelector("slot");
slot.addEventListener("slotchange", (e) => {
firstOptionObserver.disconnect(),
lastOptionObserver.disconnect(),
this.observeSelf.disconnect(),
clearTimeout(this.slotChangeTimeout),
(this.slotChangeTimeout = setTimeout(() => {
(this.assignedElements = t.assignedElements()),
this.assignedElements.forEach((t) => {
t.hasAttribute("selected") && (this._value = t.value);
}),
(this.assignedElements = slot.assignedElements()),
this.observeSelf.observe(this);
}, 0));
}),
new ResizeObserver((entries) => {
entries.forEach((entry) => {
if (entry.contentBoxSize) {
const contentBoxSize = Array.isArray(entry.contentBoxSize)
? entry.contentBoxSize[0]
: entry.contentBoxSize;
this.scrollDistance = 0.6 * contentBoxSize.inlineSize;
} else this.scrollDistance = 0.6 * entry.contentRect.width;
});
const e = new ResizeObserver((t) => {
t.forEach((t) => {
if (t.contentBoxSize) {
const e = Array.isArray(t.contentBoxSize)
? t.contentBoxSize[0]
: t.contentBoxSize;
this.scrollDistance = 0.6 * e.inlineSize;
} else this.scrollDistance = 0.6 * t.contentRect.width;
});
});
e.observe(this),
}).observe(this),
(this.observeSelf = new IntersectionObserver(
(t, e) => {
t.forEach((t) => {
t.isIntersecting &&
(entries, observer) => {
entries.forEach((entry) => {
entry.isIntersecting &&
!this.hasAttribute("multiline") &&
this.assignedElements.length > 0 &&
(n.observe(this.assignedElements[0]),
i.observe(
(firstOptionObserver.observe(this.assignedElements[0]),
lastOptionObserver.observe(
this.assignedElements[this.assignedElements.length - 1]
),
e.unobserve(this));
observer.unobserve(this));
});
},
{ threshold: 1 }
)),
this.chipsWrapper.addEventListener("option-clicked", (t) => {
this._value !== t.target.value &&
(this.setSelectedOption(t.target.value), this.fireEvent());
this.chipsWrapper.addEventListener("option-clicked", (e) => {
e.stopPropagation(),
this._value !== e.detail.value &&
(this.setSelectedOption(e.detail.value), this.fireEvent());
});
const n = new IntersectionObserver(
(t) => {
t.forEach((t) => {
t.isIntersecting
const firstOptionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
entry.isIntersecting
? (this.navButtonLeft.classList.add("hide"),
this.coverLeft.classList.add("hide"))
: (this.navButtonLeft.classList.remove("hide"),
@ -124,10 +124,10 @@ const smChips = document.createElement("template");
},
{ threshold: 1, root: this }
),
i = new IntersectionObserver(
(t) => {
t.forEach((t) => {
t.isIntersecting
lastOptionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
entry.isIntersecting
? (this.navButtonRight.classList.add("hide"),
this.coverRight.classList.add("hide"))
: (this.navButtonRight.classList.remove("hide"),
@ -156,11 +156,14 @@ const smChip = document.createElement("template");
this.attachShadow({ mode: "open" }).append(
smChip.content.cloneNode(!0)
),
(this._value = void 0),
(this._value = this.getAttribute("value")),
(this.radioButton = this.shadowRoot.querySelector("input")),
(this.fireEvent = this.fireEvent.bind(this)),
(this.handleKeyDown = this.handleKeyDown.bind(this));
}
static get observedAttributes() {
return ["selected"];
}
get value() {
return this._value;
}
@ -173,16 +176,25 @@ const smChip = document.createElement("template");
})
);
}
handleKeyDown(t) {
("Enter" !== t.key && "Space" !== t.key) || this.fireEvent();
handleKeyDown(e) {
("Enter" !== e.key && "Space" !== e.key) || this.fireEvent();
}
connectedCallback() {
this.setAttribute("role", "option"),
this.setAttribute("tabindex", "0"),
(this._value = this.getAttribute("value")),
this.hasAttribute("value") ||
console.error("sm-chip must have a value attribute"),
this.hasAttribute("selected") && this.fireEvent(),
this.addEventListener("click", this.fireEvent),
this.addEventListener("keydown", this.handleKeyDown);
}
attributeChangedCallback(name, oldValue, newValue) {
"selected" === name
? this.hasAttribute("selected")
? (this.fireEvent(), this.setAttribute("aria-selected", "true"))
: this.removeAttribute("aria-selected")
: "value" === name && (this._value = newValue);
}
disconnectedCallback() {
this.removeEventListener("click", this.fireEvent),
this.removeEventListener("keydown", this.handleKeyDown);
@ -354,11 +366,6 @@ const smForm = document.createElement("template");
this.resetButton.addEventListener("click", this.reset),
this._checkValidity();
};
checkIfSupported = (elem) =>
1 === elem.nodeType &&
(elem.tagName.includes("-") ||
"input" === elem.tagName ||
elem.querySelector(this.supportedElements));
connectedCallback() {
const updateFormDecedents = this.debounce(this.elementsChanged, 100);
this.addEventListener("input", this.debounce(this._checkValidity, 100)),
@ -372,11 +379,15 @@ const smForm = document.createElement("template");
(this.mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
(("childList" === mutation.type &&
[...mutation.addedNodes].some((node) =>
this.checkIfSupported(node)
[...mutation.addedNodes].some(
(node) =>
1 === node.nodeType &&
node.querySelector(this.supportedElements)
)) ||
[...mutation.removedNodes].some((node) =>
this.checkIfSupported(node)
[...mutation.removedNodes].some(
(node) =>
1 === node.nodeType &&
node.querySelector(this.supportedElements)
)) &&
updateFormDecedents();
});
@ -384,8 +395,7 @@ const smForm = document.createElement("template");
this.mutationObserver.observe(this, { childList: !0, subtree: !0 });
}
attributeChangedCallback(name, oldValue, newValue) {
"skip-submit" === name &&
(this.skipSubmit = this.hasAttribute("skip-submit"));
"skip-submit" === name && (this.skipSubmit = null !== newValue);
}
disconnectedCallback() {
this.removeEventListener(
@ -535,7 +545,7 @@ const smInput = document.createElement("template");
let _validity = { isValid: !0, errorText: "" };
return (
this.validationFunction &&
(_validity = this.validationFunction(this.input.value)),
(_validity = this.validationFunction(this.input.value, this)),
_isValid && _validity.isValid
? (this.setAttribute("valid", ""),
this.removeAttribute("invalid"),
@ -1493,6 +1503,362 @@ const popupStack = new Stack(),
}
}
);
const smSwitch = document.createElement("template");
(smSwitch.innerHTML =
'\t<style> *{ box-sizing: border-box; padding: 0; margin: 0; } :host{ display: inline-flex; } :host(:active) .thumb{ box-shadow: 0 0.1rem 0.4rem #00000060, 0 0 0 0.2rem white inset; } label{ display: flex; align-items: center; width: 100%; outline: none; cursor: pointer; -webkit-tap-highlight-color: transparent; } :host([disabled]) { cursor: not-allowed; opacity: 0.6; pointer-events: none; } .switch { position: relative; display: flex; align-items: center; width: 2.4rem; flex-shrink: 0; margin-left: auto; padding: 0.2rem; cursor: pointer; border-radius: 2rem; } input { display: none; } .track { position: absolute; left: 0; right: 0; height: 1.4rem; transition: background 0.3s; background: rgba(var(--text-color,inherit), 0.4); box-shadow: 0 0.1rem 0.3rem #00000040 inset; border-radius: 1rem; } label:focus-visible .thumb::after{ opacity: 1; } .thumb::after{ content: \'\'; display: flex; position: absolute; height: 2.6rem; width: 2.6rem; background: rgba(var(--text-color,inherit), 0.2); border-radius: 2rem; opacity: 0; transition: opacity 0.3s; } .thumb { position: relative; display: inline-flex; height: 1rem; width: 1rem; justify-content: center; align-items: center; border-radius: 1rem; box-shadow: 0 0.1rem 0.4rem #00000060, 0 0 0 0.2rem white inset; transition: 0.3s ease; background-color: inherit; } input:checked ~ .thumb { transform: translateX(100%); } input:checked ~ .track { background: var(--accent-color, teal); }</style><label tabindex="0"> <slot name="left"></slot> <div part="switch" class="switch"> <input type="checkbox"> <div class="track"></div> <div class="thumb"></div> </div> <slot name="right"></slot></label>'),
customElements.define(
"sm-switch",
class extends HTMLElement {
constructor() {
super(),
this.attachShadow({ mode: "open" }).append(
smSwitch.content.cloneNode(!0)
),
(this.switch = this.shadowRoot.querySelector(".switch")),
(this.input = this.shadowRoot.querySelector("input")),
(this.isChecked = !1),
(this.isDisabled = !1);
}
static get observedAttributes() {
return ["disabled", "checked"];
}
get disabled() {
return this.isDisabled;
}
set disabled(val) {
val
? this.setAttribute("disabled", "")
: this.removeAttribute("disabled");
}
get checked() {
return this.isChecked;
}
set checked(value) {
value
? this.setAttribute("checked", "")
: this.removeAttribute("checked");
}
get value() {
return this.isChecked;
}
reset() {}
dispatch = () => {
this.dispatchEvent(
new CustomEvent("change", {
bubbles: !0,
composed: !0,
detail: { value: this.isChecked },
})
);
};
connectedCallback() {
this.addEventListener("keydown", (e) => {
" " !== e.key ||
this.isDisabled ||
(e.preventDefault(), this.input.click());
}),
this.input.addEventListener("click", (e) => {
this.input.checked ? (this.checked = !0) : (this.checked = !1),
this.dispatch();
});
}
attributeChangedCallback(name, oldValue, newValue) {
oldValue !== newValue &&
("disabled" === name
? this.hasAttribute("disabled")
? ((this.disabled = !0), (this.inert = !0))
: ((this.disabled = !1), (this.inert = !1))
: "checked" === name &&
(this.hasAttribute("checked")
? ((this.isChecked = !0), (this.input.checked = !0))
: ((this.isChecked = !1), (this.input.checked = !1))));
}
}
);
const smSelect = document.createElement("template");
(smSelect.innerHTML =
'<style> *{ padding: 0; margin: 0; box-sizing: border-box;} :host{ display: flex;}:host([disabled]) .select{ opacity: 0.6; cursor: not-allowed;}:host([readonly]) .select{ cursor: default; pointer-events: none;}.select{ position: relative; display: flex; flex-direction: column; cursor: pointer; width: 100%; -webkit-tap-highlight-color: transparent;}.icon { height: 1.2rem; width: 1.2rem; margin-left: 0.5rem; fill: rgba(var(--text-color, (17,17,17)), 0.7);} .selected-option-text{ font-size: inherit; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500;}.selection{ border-radius: var(--select-border-radius,0.5rem); display: grid; grid-template-columns: 1fr auto; grid-template-areas: \'heading heading\' \'. .\'; padding: var(--padding,0.6rem 0.8rem); background: var(--background, rgba(var(--text-color,(17,17,17)), 0.06)); align-items: center; outline: none; z-index: 2; height: 100%; align-content: center;}.selection:focus{ box-shadow: 0 0 0 0.1rem var(--accent-color, teal) inset; }:host([align-select="left"]) .options{ left: 0;}:host([align-select="right"]) .options{ right: 0;}.options{ top: 100%; padding: var(--options-padding, 0.3rem); margin-top: 0.2rem; overflow: hidden auto; position: absolute; grid-area: options; display: flex; flex-direction: column; width: var(--options-width, 100%); min-width: var(--min-width, auto); max-height: var(--max-height, auto); background: rgba(var(--foreground-color,(255,255,255)), 1); border: solid 1px rgba(var(--text-color,(17,17,17)), 0.2); border-radius: var(--options-border-radius, 0.5rem); z-index: 1; box-shadow: 0 1rem 1.5rem rgba(0 0 0 /0.2);}:host([isUnder]) .options{ top: auto; bottom: 100%; margin-top: 0; margin-bottom: 0.2rem; box-shadow: 0 -1rem 1.5rem rgba(0 0 0 /0.2);}:host([open]) .icon--expand{ display: none;}:host([open]) .icon--collapse{ display: block;}.icon--expand{ display: block;}.icon--collapse{ display: none;}.hidden{ display: none;}@media (any-hover: hover){ ::-webkit-scrollbar{ width: 0.5rem; height: 0.5rem; } ::-webkit-scrollbar-thumb{ background: rgba(var(--text-color,(17,17,17)), 0.3); border-radius: 1rem; &:hover{ background: rgba(var(--text-color,(17,17,17)), 0.5); } }}</style><div class="select"> <div class="selection" part="button"> <div class="selected-option-text"></div> <svg class="icon icon--expand" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z"/></svg> <svg class="icon icon--collapse" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M24 0v24H0V0h24z" fill="none" opacity=".87"/><path d="M7.41 18.59L8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"/></svg> </div> <div part="options" class="options hidden"> <slot></slot> </div></div>'),
customElements.define(
"sm-select",
class extends HTMLElement {
constructor() {
super(),
this.attachShadow({ mode: "open" }).append(
smSelect.content.cloneNode(!0)
),
(this.focusIn = this.focusIn.bind(this)),
(this.reset = this.reset.bind(this)),
(this.open = this.open.bind(this)),
(this.collapse = this.collapse.bind(this)),
(this.toggle = this.toggle.bind(this)),
(this.handleOptionsNavigation =
this.handleOptionsNavigation.bind(this)),
(this.handleOptionSelection = this.handleOptionSelection.bind(this)),
(this.handleKeydown = this.handleKeydown.bind(this)),
(this.handleClickOutside = this.handleClickOutside.bind(this)),
(this.selectOption = this.selectOption.bind(this)),
(this.debounce = this.debounce.bind(this)),
(this.elementsChanged = this.elementsChanged.bind(this)),
(this.availableOptions = []),
this.previousOption,
(this.isOpen = !1),
(this.label = ""),
(this.defaultSelected = ""),
(this.isUnderViewport = !1),
(this.animationOptions = {
duration: 300,
fill: "forwards",
easing: "ease",
}),
(this.optionList = this.shadowRoot.querySelector(".options")),
(this.selection = this.shadowRoot.querySelector(".selection")),
(this.selectedOptionText = this.shadowRoot.querySelector(
".selected-option-text"
));
}
static get observedAttributes() {
return ["disabled", "label", "readonly"];
}
get value() {
return this.getAttribute("value");
}
set value(t) {
const e = this.availableOptions.find(
(e) => e.getAttribute("value") === t
);
e
? (this.setAttribute("value", t), this.selectOption(e))
: console.warn(`There is no option with ${t} as value`);
}
debounce(t, e) {
let n = null;
return (...i) => {
window.clearTimeout(n),
(n = window.setTimeout(() => {
t.apply(null, i);
}, e));
};
}
reset(t = !0) {
if (
this.availableOptions[0] &&
this.previousOption !== this.availableOptions[0]
) {
const e =
this.availableOptions.find((t) => t.hasAttribute("selected")) ||
this.availableOptions[0];
(this.value = e.getAttribute("value")), t && this.fireEvent();
}
}
selectOption(t) {
this.previousOption !== t &&
(this.querySelectorAll("[selected").forEach((t) =>
t.removeAttribute("selected")
),
(this.selectedOptionText.textContent = `${this.label}${t.textContent}`),
t.setAttribute("selected", ""),
(this.previousOption = t));
}
focusIn() {
this.selection.focus();
}
open() {
this.availableOptions.forEach((t) => t.setAttribute("tabindex", 0)),
this.optionList.classList.remove("hidden"),
(this.isUnderViewport =
this.getBoundingClientRect().bottom +
this.optionList.getBoundingClientRect().height >
window.innerHeight),
this.isUnderViewport
? this.setAttribute("isUnder", "")
: this.removeAttribute("isUnder"),
this.optionList.animate(
[
{
transform: `translateY(${
this.isUnderViewport ? "" : "-"
}0.5rem)`,
opacity: 0,
},
{ transform: "translateY(0)", opacity: 1 },
],
this.animationOptions
),
this.setAttribute("open", ""),
(this.style.zIndex = 1e3),
(
this.availableOptions.find((t) => t.hasAttribute("selected")) ||
this.availableOptions[0]
).focus(),
document.addEventListener("mousedown", this.handleClickOutside),
(this.isOpen = !0);
}
collapse() {
this.removeAttribute("open"),
(this.optionList.animate(
[
{ transform: "translateY(0)", opacity: 1 },
{
transform: `translateY(${
this.isUnderViewport ? "" : "-"
}0.5rem)`,
opacity: 0,
},
],
this.animationOptions
).onfinish = () => {
this.availableOptions.forEach((t) => t.removeAttribute("tabindex")),
document.removeEventListener(
"mousedown",
this.handleClickOutside
),
this.optionList.classList.add("hidden"),
(this.isOpen = !1),
(this.style.zIndex = "auto");
});
}
toggle() {
this.isOpen || this.hasAttribute("disabled")
? this.collapse()
: this.open();
}
fireEvent() {
this.dispatchEvent(
new CustomEvent("change", {
bubbles: !0,
composed: !0,
detail: { value: this.value },
})
);
}
handleOptionsNavigation(t) {
"ArrowUp" === t.key
? (t.preventDefault(),
document.activeElement.previousElementSibling
? document.activeElement.previousElementSibling.focus()
: this.availableOptions[this.availableOptions.length - 1].focus())
: "ArrowDown" === t.key &&
(t.preventDefault(),
document.activeElement.nextElementSibling
? document.activeElement.nextElementSibling.focus()
: this.availableOptions[0].focus());
}
handleOptionSelection(t) {
this.previousOption !== document.activeElement &&
((this.value = document.activeElement.getAttribute("value")),
this.fireEvent());
}
handleClick(t) {
t.target === this
? this.toggle()
: (this.handleOptionSelection(), this.collapse());
}
handleKeydown(t) {
t.target === this
? this.isOpen && "ArrowDown" === t.key
? (t.preventDefault(),
(
this.availableOptions.find((t) => t.hasAttribute("selected")) ||
this.availableOptions[0]
).focus(),
this.handleOptionSelection(t))
: " " === t.key && (t.preventDefault(), this.toggle())
: (this.handleOptionsNavigation(t),
this.handleOptionSelection(t),
["Enter", " ", "Escape", "Tab"].includes(t.key) &&
(t.preventDefault(), this.collapse(), this.focusIn()));
}
handleClickOutside(t) {
this.isOpen && !this.contains(t.target) && this.collapse();
}
elementsChanged() {
(this.availableOptions = [...this.querySelectorAll("sm-option")]),
this.reset(!1),
(this.defaultSelected = this.value);
}
connectedCallback() {
this.setAttribute("role", "listbox"),
this.hasAttribute("disabled") ||
this.hasAttribute("readonly") ||
(this.selection.setAttribute("tabindex", "0"),
this.addEventListener("click", this.handleClick),
this.addEventListener("keydown", this.handleKeydown));
const t = this.debounce(this.elementsChanged, 100);
this.shadowRoot.querySelector("slot").addEventListener("slotchange", t),
(this.mutationObserver = new MutationObserver((e) => {
let n = !1;
if (
(e.forEach((e) => {
switch (e.type) {
case "childList":
t();
break;
case "attributes":
n = !0;
}
}),
n)
) {
const t =
this.availableOptions.find((t) => t.hasAttribute("selected")) ||
this.availableOptions[0];
(this.selectedOptionText.textContent = `${this.label}${t.textContent}`),
this.setAttribute("value", t.getAttribute("value"));
}
})),
this.mutationObserver.observe(this, {
subtree: !0,
childList: !0,
attributeFilter: ["selected"],
}),
new IntersectionObserver((t, e) => {
t.forEach((t) => {
if (t.isIntersecting) {
this.selection.getBoundingClientRect().left <
window.innerWidth / 2
? this.setAttribute("align-select", "left")
: this.setAttribute("align-select", "right");
}
});
}).observe(this);
}
disconnectedCallback() {
this.removeEventListener("click", this.handleClick),
this.removeEventListener("keydown", this.handleKeydown);
}
attributeChangedCallback(t) {
"disabled" === t || "readonly" === t
? this.hasAttribute("disabled") || this.hasAttribute("readonly")
? (this.selection.removeAttribute("tabindex"),
this.removeEventListener("click", this.handleClick),
this.removeEventListener("keydown", this.handleKeydown))
: (this.selection.setAttribute("tabindex", "0"),
this.addEventListener("click", this.handleClick),
this.addEventListener("keydown", this.handleKeydown))
: "label" === t &&
(this.label = this.hasAttribute("label")
? `${this.getAttribute("label")} `
: "");
}
}
);
const smOption = document.createElement("template");
(smOption.innerHTML =
'<style> *{ padding: 0; margin: 0; -webkit-box-sizing: border-box; box-sizing: border-box;} :host{ display: -webkit-box; display: -ms-flexbox; display: flex; overflow: hidden; border-radius: var(--border-radius, 0.3rem);}.option{ position: relative; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; width: 100%; gap: 0.5rem; padding: var(--padding, 0.6rem 1rem); cursor: pointer; outline: none; user-select: none;}.option::before{ position: absolute; content: \'\'; display: block; width: 0.2rem; height: 1em; left: 0; border-radius: 0 1em 1em 0; background: rgba(var(--text-color,(17,17,17)), 0.5); transition: all 0.2s ease-in-out; opacity: 0;}:host(:focus){ outline: none; background: rgba(var(--text-color,(17,17,17)), 0.1);}:host(:focus) .option::before{ opacity: 1}:host([selected]) .option::before{ opacity: 1; background: var(--accent-color, teal);}@media (hover: hover){ .option:hover{ background: rgba(var(--text-color,(17,17,17)), 0.1); } :host(:not([selected]):hover) .option::before{ opacity: 1 }}</style><div class="option" part="option"> <slot></slot> </div>'),
customElements.define(
"sm-option",
class extends HTMLElement {
constructor() {
super(),
this.attachShadow({ mode: "open" }).append(
smOption.content.cloneNode(!0)
);
}
connectedCallback() {
this.setAttribute("role", "option");
}
}
);
const spinner = document.createElement("template");
spinner.innerHTML =
'<style> *{ padding: 0; margin: 0; -webkit-box-sizing: border-box; box-sizing: border-box;}.loader { display: flex; height: var(--size, 1.5rem); width: var(--size, 1.5rem); stroke-width: 8; overflow: visible; stroke: var(--accent-color, teal); fill: none; stroke-dashoffset: 180; stroke-dasharray: 180; animation: load 2s infinite, spin 1s linear infinite;}@keyframes load { 50% { stroke-dashoffset: 0; } 100%{ stroke-dashoffset: -180; }}@keyframes spin { 100% { transform: rotate(360deg); }}</style><svg viewBox="0 0 64 64" class="loader"><circle cx="32" cy="32" r="32" /></svg>';