Compare commits

...

10 Commits

Author SHA1 Message Date
SaketAnand
e9bda5421f Update deployment workflow (main + master support + cleanup)
All checks were successful
Deploy to Dappbundle / Deploy (push) Successful in 32s
2026-03-06 17:39:31 +05:30
SaketAnand
973aa058c7 Update Gitea deployment workflow
All checks were successful
Deploy to Dappbundle / Deploy (push) Successful in 17s
2026-03-06 01:56:48 +05:30
89838522a5
Merge pull request #2 from void-57/main
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
Add transaction details and transaction history features
2026-01-14 18:34:30 +05:30
655e59c850 Enhance transaction pagination and filtering functionality with improved caching and UI updates 2026-01-14 18:01:27 +05:30
28c819ec85 Enhance transaction processing to accurately extract sender, receiver, and transaction types (transfer, swap, nft_mint) 2026-01-14 17:20:14 +05:30
774abf7bd6 Enhance transaction detail extraction to identify sender, receiver, and transaction types (transfer, swap, nft_mint) 2026-01-14 01:40:01 +05:30
a5ebc25567 Calculate transaction fee and adjust transfer amount accordingly 2026-01-13 23:10:06 +05:30
5666fe5d34 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.
2026-01-13 22:14:40 +05:30
SaketAnand
a20fd7cd05
Merge pull request #1 from void-57/main
Add workflow for pushing updates to Dappbundle on push events
2026-01-13 16:18:40 +05:30
dfa3337226 Add workflow for pushing updates to Dappbundle on push events 2026-01-13 16:08:50 +05:30
4 changed files with 4148 additions and 1361 deletions

54
.github/workflows/push-dappbundle.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: Deploy to Dappbundle
on:
push:
branches:
- main
- master
jobs:
build:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.R_HOST }}
username: ${{ secrets.P_USERNAME }}
password: ${{ secrets.P_PASSWORD }}
port: ${{ secrets.SSH_PORT }}
script: |
set -e
BASE="${{ secrets.DEPLOYMENT_LOCATION }}"
APP="${{ github.event.repository.name }}"
echo "== Ensuring dappbundle repo exists =="
if [ ! -d "$BASE/dappbundle/.git" ]; then
git clone git@gitea.ranchimall.net:RanchiMall/dappbundle.git "$BASE/dappbundle"
else
cd "$BASE/dappbundle"
git pull
fi
echo "== Refreshing app bundle =="
rm -rf "$BASE/dappbundle/$APP"
git clone git@gitea.ranchimall.net:RanchiMall/$APP.git "$BASE/dappbundle/$APP"
echo "== Cleaning git metadata =="
cd "$BASE/dappbundle/$APP"
rm -rf .git .github .gitignore .gitattributes
cd "$BASE/dappbundle"
git config user.email "ranchimallfze@gmail.com"
git config user.name "ranchimall"
git add .
git commit -m "Auto-update $APP" || echo "No changes"
git push

View File

@ -28,7 +28,7 @@ body {
background-color: rgba(var(--foreground-color), 1); background-color: rgba(var(--foreground-color), 1);
} }
body[data-theme=dark] { body[data-theme="dark"] {
--accent-color: #92a2ff; --accent-color: #92a2ff;
--accent-color-rgb: 160, 182, 255; --accent-color-rgb: 160, 182, 255;
--secondary-color: #d60739; --secondary-color: #d60739;
@ -39,7 +39,7 @@ body[data-theme=dark] {
--green: #00e676; --green: #00e676;
--yellow: rgb(255, 213, 5); --yellow: rgb(255, 213, 5);
} }
body[data-theme=dark] ::-webkit-calendar-picker-indicator { body[data-theme="dark"] ::-webkit-calendar-picker-indicator {
filter: invert(1); filter: invert(1);
} }
@ -90,7 +90,7 @@ a:any-link:focus-visible {
outline: rgba(var(--text-color), 1) 0.1rem solid; outline: rgba(var(--text-color), 1) 0.1rem solid;
} }
input[type=datetime-local] { input[type="datetime-local"] {
width: 100%; width: 100%;
padding: 0.8rem 0.6rem; padding: 0.8rem 0.6rem;
border: none; border: none;
@ -101,7 +101,7 @@ input[type=datetime-local] {
color: inherit; color: inherit;
background-color: rgba(var(--text-color), 0.06); background-color: rgba(var(--text-color), 0.06);
} }
input[type=datetime-local]:focus { input[type="datetime-local"]:focus {
outline: none; outline: none;
box-shadow: 0 0 0 0.1rem var(--accent-color); box-shadow: 0 0 0 0.1rem var(--accent-color);
} }
@ -604,7 +604,11 @@ ul {
position: absolute; position: absolute;
border-radius: 50%; border-radius: 50%;
transform: scale(0); 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; pointer-events: none;
} }
@ -699,17 +703,17 @@ ul {
justify-self: flex-start; justify-self: flex-start;
} }
ul[type=circle], ul[type="circle"],
menu[type=circle] { menu[type="circle"] {
padding: 1.5rem 2.5rem; padding: 1.5rem 2.5rem;
list-style: circle; list-style: circle;
} }
ul[type=circle] li, ul[type="circle"] li,
menu[type=circle] li { menu[type="circle"] li {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
ul[type=circle] li:last-of-type, ul[type="circle"] li:last-of-type,
menu[type=circle] li:last-of-type { menu[type="circle"] li:last-of-type {
margin-bottom: 0; margin-bottom: 0;
} }
ul, ul,
@ -773,13 +777,13 @@ menu {
#meta_mask_status_button .icon-wrapper > * { #meta_mask_status_button .icon-wrapper > * {
grid-area: 1/1; grid-area: 1/1;
} }
#meta_mask_status_button[data-status=connected] { #meta_mask_status_button[data-status="connected"] {
pointer-events: none; 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); 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); background-color: var(--danger-color);
} }
@ -847,7 +851,8 @@ main {
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
} }
.nav-item__title { .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 { .nav-item--active {
color: var(--accent-color); color: var(--accent-color);
@ -870,17 +875,17 @@ main {
overflow: auto; overflow: auto;
grid-area: pages; grid-area: pages;
} }
#page_container[data-page=home] > :nth-child(2) { #page_container[data-page="home"] > :nth-child(2) {
flex: 1; flex: 1;
} }
#page_container[data-page=send] { #page_container[data-page="send"] {
align-items: flex-start; align-items: flex-start;
} }
#page_container[data-page=send] > * { #page_container[data-page="send"] > * {
padding: 1rem; padding: 1rem;
margin: 0 auto; margin: 0 auto;
} }
#page_container[data-page=create] { #page_container[data-page="create"] {
margin: 0 auto; margin: 0 auto;
padding: 4vw 1rem; padding: 4vw 1rem;
gap: 2rem; gap: 2rem;
@ -951,6 +956,19 @@ aside h4 {
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
#address_transactions {
width: 100%;
max-width: 32rem;
}
.transaction {
width: 100%;
}
#transactions_list {
width: 100%;
}
#error_section { #error_section {
display: grid; display: grid;
height: 100%; height: 100%;
@ -978,6 +996,7 @@ aside h4 {
position: relative; position: relative;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.transaction__phase:not(:last-of-type)::after { .transaction__phase:not(:last-of-type)::after {
content: ""; content: "";
position: absolute; position: absolute;
@ -1068,17 +1087,24 @@ aside h4 {
padding-bottom: 1rem; padding-bottom: 1rem;
border-bottom: solid thin rgba(var(--text-color), 0.3); 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) { @media only screen and (max-width: 640px) {
.hide-on-small { .hide-on-small {
display: none; display: none;
} }
#page_container[data-page=home] { #page_container[data-page="home"] {
flex-direction: column; flex-direction: column;
} }
#page_container[data-page=home] > :first-child { #page_container[data-page="home"] > :first-child {
order: 1; order: 1;
} }
.create-buttons {
display: grid;
}
} }
@media only screen and (min-width: 640px) { @media only screen and (min-width: 640px) {
sm-popup { sm-popup {
@ -1172,3 +1198,378 @@ aside h4 {
animation: none !important; 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;
}

2584
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() { get value() {
return this._value; return this._value;
} }
set value(t) { set value(val) {
this.setSelectedOption(t); this.setSelectedOption(val);
}
get isValid() {
return void 0 !== this._value;
} }
scrollLeft() { scrollLeft() {
this.chipsWrapper.scrollBy({ this.chipsWrapper.scrollBy({
@ -43,18 +46,18 @@ const smChips = document.createElement("template");
behavior: "smooth", behavior: "smooth",
}); });
} }
setSelectedOption(t) { setSelectedOption(value) {
this._value !== t && this._value !== value &&
((this._value = t), ((this._value = value),
this.assignedElements.forEach((e) => { this.assignedElements.forEach((elem) => {
e.value == t elem.value == value
? (e.setAttribute("selected", ""), ? (elem.setAttribute("selected", ""),
e.scrollIntoView({ elem.scrollIntoView({
behavior: "smooth", behavior: "smooth",
block: "nearest", block: "nearest",
inline: "center", inline: "center",
})) }))
: e.removeAttribute("selected"); : elem.removeAttribute("selected");
})); }));
} }
fireEvent() { fireEvent() {
@ -68,54 +71,51 @@ const smChips = document.createElement("template");
} }
connectedCallback() { connectedCallback() {
this.setAttribute("role", "listbox"); this.setAttribute("role", "listbox");
const t = this.shadowRoot.querySelector("slot"); const slot = this.shadowRoot.querySelector("slot");
t.addEventListener("slotchange", (e) => { slot.addEventListener("slotchange", (e) => {
n.disconnect(), firstOptionObserver.disconnect(),
i.disconnect(), lastOptionObserver.disconnect(),
this.observeSelf.disconnect(), this.observeSelf.disconnect(),
clearTimeout(this.slotChangeTimeout), clearTimeout(this.slotChangeTimeout),
(this.slotChangeTimeout = setTimeout(() => { (this.slotChangeTimeout = setTimeout(() => {
(this.assignedElements = t.assignedElements()), (this.assignedElements = slot.assignedElements()),
this.assignedElements.forEach((t) => {
t.hasAttribute("selected") && (this._value = t.value);
}),
this.observeSelf.observe(this); this.observeSelf.observe(this);
}, 0)); }, 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) => { }).observe(this),
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),
(this.observeSelf = new IntersectionObserver( (this.observeSelf = new IntersectionObserver(
(t, e) => { (entries, observer) => {
t.forEach((t) => { entries.forEach((entry) => {
t.isIntersecting && entry.isIntersecting &&
!this.hasAttribute("multiline") && !this.hasAttribute("multiline") &&
this.assignedElements.length > 0 && this.assignedElements.length > 0 &&
(n.observe(this.assignedElements[0]), (firstOptionObserver.observe(this.assignedElements[0]),
i.observe( lastOptionObserver.observe(
this.assignedElements[this.assignedElements.length - 1] this.assignedElements[this.assignedElements.length - 1]
), ),
e.unobserve(this)); observer.unobserve(this));
}); });
}, },
{ threshold: 1 } { threshold: 1 }
)), )),
this.chipsWrapper.addEventListener("option-clicked", (t) => { this.chipsWrapper.addEventListener("option-clicked", (e) => {
this._value !== t.target.value && e.stopPropagation(),
(this.setSelectedOption(t.target.value), this.fireEvent()); this._value !== e.detail.value &&
(this.setSelectedOption(e.detail.value), this.fireEvent());
}); });
const n = new IntersectionObserver( const firstOptionObserver = new IntersectionObserver(
(t) => { (entries) => {
t.forEach((t) => { entries.forEach((entry) => {
t.isIntersecting entry.isIntersecting
? (this.navButtonLeft.classList.add("hide"), ? (this.navButtonLeft.classList.add("hide"),
this.coverLeft.classList.add("hide")) this.coverLeft.classList.add("hide"))
: (this.navButtonLeft.classList.remove("hide"), : (this.navButtonLeft.classList.remove("hide"),
@ -124,10 +124,10 @@ const smChips = document.createElement("template");
}, },
{ threshold: 1, root: this } { threshold: 1, root: this }
), ),
i = new IntersectionObserver( lastOptionObserver = new IntersectionObserver(
(t) => { (entries) => {
t.forEach((t) => { entries.forEach((entry) => {
t.isIntersecting entry.isIntersecting
? (this.navButtonRight.classList.add("hide"), ? (this.navButtonRight.classList.add("hide"),
this.coverRight.classList.add("hide")) this.coverRight.classList.add("hide"))
: (this.navButtonRight.classList.remove("hide"), : (this.navButtonRight.classList.remove("hide"),
@ -156,11 +156,14 @@ const smChip = document.createElement("template");
this.attachShadow({ mode: "open" }).append( this.attachShadow({ mode: "open" }).append(
smChip.content.cloneNode(!0) smChip.content.cloneNode(!0)
), ),
(this._value = void 0), (this._value = this.getAttribute("value")),
(this.radioButton = this.shadowRoot.querySelector("input")), (this.radioButton = this.shadowRoot.querySelector("input")),
(this.fireEvent = this.fireEvent.bind(this)), (this.fireEvent = this.fireEvent.bind(this)),
(this.handleKeyDown = this.handleKeyDown.bind(this)); (this.handleKeyDown = this.handleKeyDown.bind(this));
} }
static get observedAttributes() {
return ["selected"];
}
get value() { get value() {
return this._value; return this._value;
} }
@ -173,16 +176,25 @@ const smChip = document.createElement("template");
}) })
); );
} }
handleKeyDown(t) { handleKeyDown(e) {
("Enter" !== t.key && "Space" !== t.key) || this.fireEvent(); ("Enter" !== e.key && "Space" !== e.key) || this.fireEvent();
} }
connectedCallback() { connectedCallback() {
this.setAttribute("role", "option"), this.setAttribute("role", "option"),
this.setAttribute("tabindex", "0"), 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("click", this.fireEvent),
this.addEventListener("keydown", this.handleKeyDown); 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() { disconnectedCallback() {
this.removeEventListener("click", this.fireEvent), this.removeEventListener("click", this.fireEvent),
this.removeEventListener("keydown", this.handleKeyDown); this.removeEventListener("keydown", this.handleKeyDown);
@ -354,11 +366,6 @@ const smForm = document.createElement("template");
this.resetButton.addEventListener("click", this.reset), this.resetButton.addEventListener("click", this.reset),
this._checkValidity(); this._checkValidity();
}; };
checkIfSupported = (elem) =>
1 === elem.nodeType &&
(elem.tagName.includes("-") ||
"input" === elem.tagName ||
elem.querySelector(this.supportedElements));
connectedCallback() { connectedCallback() {
const updateFormDecedents = this.debounce(this.elementsChanged, 100); const updateFormDecedents = this.debounce(this.elementsChanged, 100);
this.addEventListener("input", this.debounce(this._checkValidity, 100)), this.addEventListener("input", this.debounce(this._checkValidity, 100)),
@ -372,11 +379,15 @@ const smForm = document.createElement("template");
(this.mutationObserver = new MutationObserver((mutations) => { (this.mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => { mutations.forEach((mutation) => {
(("childList" === mutation.type && (("childList" === mutation.type &&
[...mutation.addedNodes].some((node) => [...mutation.addedNodes].some(
this.checkIfSupported(node) (node) =>
1 === node.nodeType &&
node.querySelector(this.supportedElements)
)) || )) ||
[...mutation.removedNodes].some((node) => [...mutation.removedNodes].some(
this.checkIfSupported(node) (node) =>
1 === node.nodeType &&
node.querySelector(this.supportedElements)
)) && )) &&
updateFormDecedents(); updateFormDecedents();
}); });
@ -384,8 +395,7 @@ const smForm = document.createElement("template");
this.mutationObserver.observe(this, { childList: !0, subtree: !0 }); this.mutationObserver.observe(this, { childList: !0, subtree: !0 });
} }
attributeChangedCallback(name, oldValue, newValue) { attributeChangedCallback(name, oldValue, newValue) {
"skip-submit" === name && "skip-submit" === name && (this.skipSubmit = null !== newValue);
(this.skipSubmit = this.hasAttribute("skip-submit"));
} }
disconnectedCallback() { disconnectedCallback() {
this.removeEventListener( this.removeEventListener(
@ -535,7 +545,7 @@ const smInput = document.createElement("template");
let _validity = { isValid: !0, errorText: "" }; let _validity = { isValid: !0, errorText: "" };
return ( return (
this.validationFunction && this.validationFunction &&
(_validity = this.validationFunction(this.input.value)), (_validity = this.validationFunction(this.input.value, this)),
_isValid && _validity.isValid _isValid && _validity.isValid
? (this.setAttribute("valid", ""), ? (this.setAttribute("valid", ""),
this.removeAttribute("invalid"), 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"); const spinner = document.createElement("template");
spinner.innerHTML = 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>'; '<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>';