liqgui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -0
- package/dist/components/glass-accordion.d.ts +15 -0
- package/dist/components/glass-accordion.js +173 -0
- package/dist/components/glass-avatar.d.ts +9 -0
- package/dist/components/glass-avatar.js +98 -0
- package/dist/components/glass-badge.d.ts +10 -0
- package/dist/components/glass-badge.js +151 -0
- package/dist/components/glass-button.d.ts +6 -0
- package/dist/components/glass-button.js +124 -0
- package/dist/components/glass-card.d.ts +8 -0
- package/dist/components/glass-card.js +102 -0
- package/dist/components/glass-dropdown.d.ts +12 -0
- package/dist/components/glass-dropdown.js +182 -0
- package/dist/components/glass-input.d.ts +8 -0
- package/dist/components/glass-input.js +151 -0
- package/dist/components/glass-modal.d.ts +11 -0
- package/dist/components/glass-modal.js +128 -0
- package/dist/components/glass-navbar.d.ts +6 -0
- package/dist/components/glass-navbar.js +84 -0
- package/dist/components/glass-progress.d.ts +12 -0
- package/dist/components/glass-progress.js +159 -0
- package/dist/components/glass-slider.d.ts +17 -0
- package/dist/components/glass-slider.js +168 -0
- package/dist/components/glass-tabs.d.ts +7 -0
- package/dist/components/glass-tabs.js +102 -0
- package/dist/components/glass-toast.d.ts +8 -0
- package/dist/components/glass-toast.js +128 -0
- package/dist/components/glass-toggle.d.ts +9 -0
- package/dist/components/glass-toggle.js +112 -0
- package/dist/components/glass-tooltip.d.ts +14 -0
- package/dist/components/glass-tooltip.js +214 -0
- package/dist/core/base-element.d.ts +4 -0
- package/dist/core/base-element.js +9 -0
- package/dist/core/curves.d.ts +22 -0
- package/dist/core/curves.js +32 -0
- package/dist/core/focus-trap.d.ts +1 -0
- package/dist/core/focus-trap.js +19 -0
- package/dist/core/glow.d.ts +3 -0
- package/dist/core/glow.js +57 -0
- package/dist/core/motion.d.ts +12 -0
- package/dist/core/motion.js +54 -0
- package/dist/core/spring-engine.d.ts +12 -0
- package/dist/core/spring-engine.js +90 -0
- package/dist/core/supports.d.ts +1 -0
- package/dist/core/supports.js +1 -0
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +40 -0
- package/package.json +47 -0
- package/src/styles/tokens.css +140 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export declare class GlassToast extends BaseElement {
|
|
3
|
+
static get observedAttributes(): string[];
|
|
4
|
+
connectedCallback(): void;
|
|
5
|
+
private getIcon;
|
|
6
|
+
private dismiss;
|
|
7
|
+
static show(message: string, variant?: "success" | "error" | "warning" | "info"): HTMLElement;
|
|
8
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
import { motion } from "../core/motion.js";
|
|
3
|
+
export class GlassToast extends BaseElement {
|
|
4
|
+
static get observedAttributes() {
|
|
5
|
+
return ["variant", "duration"];
|
|
6
|
+
}
|
|
7
|
+
connectedCallback() {
|
|
8
|
+
var _a;
|
|
9
|
+
const variant = this.getAttribute("variant") || "info";
|
|
10
|
+
const duration = parseInt(this.getAttribute("duration") || "4000");
|
|
11
|
+
this.mount(`
|
|
12
|
+
<div class="toast ${variant}">
|
|
13
|
+
<span class="icon">${this.getIcon(variant)}</span>
|
|
14
|
+
<div class="content">
|
|
15
|
+
<slot></slot>
|
|
16
|
+
</div>
|
|
17
|
+
<button class="close" aria-label="Dismiss">×</button>
|
|
18
|
+
<div class="progress"></div>
|
|
19
|
+
</div>
|
|
20
|
+
`, `
|
|
21
|
+
:host {
|
|
22
|
+
position: fixed;
|
|
23
|
+
bottom: 1.5rem;
|
|
24
|
+
right: 1.5rem;
|
|
25
|
+
z-index: 10000;
|
|
26
|
+
}
|
|
27
|
+
.toast {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: 0.75rem;
|
|
31
|
+
padding: 1rem 1.25rem;
|
|
32
|
+
background: var(--lg-bg);
|
|
33
|
+
backdrop-filter: blur(var(--lg-blur));
|
|
34
|
+
-webkit-backdrop-filter: blur(var(--lg-blur));
|
|
35
|
+
border-radius: var(--lg-radius);
|
|
36
|
+
border: 1px solid var(--lg-border);
|
|
37
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
38
|
+
min-width: 280px;
|
|
39
|
+
max-width: 400px;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
}
|
|
42
|
+
.icon {
|
|
43
|
+
font-size: 1.25rem;
|
|
44
|
+
flex-shrink: 0;
|
|
45
|
+
}
|
|
46
|
+
.content {
|
|
47
|
+
flex: 1;
|
|
48
|
+
line-height: 1.4;
|
|
49
|
+
}
|
|
50
|
+
.close {
|
|
51
|
+
background: none;
|
|
52
|
+
border: none;
|
|
53
|
+
color: inherit;
|
|
54
|
+
font-size: 1.5rem;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
opacity: 0.5;
|
|
57
|
+
padding: 0;
|
|
58
|
+
line-height: 1;
|
|
59
|
+
transition: opacity 0.2s;
|
|
60
|
+
}
|
|
61
|
+
.close:hover {
|
|
62
|
+
opacity: 1;
|
|
63
|
+
}
|
|
64
|
+
.progress {
|
|
65
|
+
position: absolute;
|
|
66
|
+
bottom: 0;
|
|
67
|
+
left: 0;
|
|
68
|
+
height: 3px;
|
|
69
|
+
background: var(--accent-color, rgba(255, 255, 255, 0.5));
|
|
70
|
+
animation: progress ${duration}ms linear forwards;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Variants */
|
|
74
|
+
.toast.success { --accent-color: #30d158; }
|
|
75
|
+
.toast.success .icon { color: #30d158; }
|
|
76
|
+
|
|
77
|
+
.toast.error { --accent-color: #ff453a; }
|
|
78
|
+
.toast.error .icon { color: #ff453a; }
|
|
79
|
+
|
|
80
|
+
.toast.warning { --accent-color: #ffd60a; }
|
|
81
|
+
.toast.warning .icon { color: #ffd60a; }
|
|
82
|
+
|
|
83
|
+
.toast.info { --accent-color: #5ac8fa; }
|
|
84
|
+
.toast.info .icon { color: #5ac8fa; }
|
|
85
|
+
|
|
86
|
+
@keyframes progress {
|
|
87
|
+
from { width: 100%; }
|
|
88
|
+
to { width: 0%; }
|
|
89
|
+
}
|
|
90
|
+
`);
|
|
91
|
+
// Animate in
|
|
92
|
+
const toast = this.root.querySelector(".toast");
|
|
93
|
+
motion.slideInUp(toast, 300);
|
|
94
|
+
// Close button
|
|
95
|
+
(_a = this.root.querySelector(".close")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => this.dismiss());
|
|
96
|
+
// Auto dismiss
|
|
97
|
+
if (duration > 0) {
|
|
98
|
+
setTimeout(() => this.dismiss(), duration);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
getIcon(variant) {
|
|
102
|
+
const icons = {
|
|
103
|
+
success: "✓",
|
|
104
|
+
error: "✕",
|
|
105
|
+
warning: "⚠",
|
|
106
|
+
info: "ℹ"
|
|
107
|
+
};
|
|
108
|
+
return icons[variant] || icons.info;
|
|
109
|
+
}
|
|
110
|
+
dismiss() {
|
|
111
|
+
const toast = this.root.querySelector(".toast");
|
|
112
|
+
if (toast) {
|
|
113
|
+
motion.slideOutDown(toast, 200).finished.then(() => this.remove());
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.remove();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Static helper for creating toasts
|
|
120
|
+
static show(message, variant = "info") {
|
|
121
|
+
const toast = document.createElement("glass-toast");
|
|
122
|
+
toast.setAttribute("variant", variant);
|
|
123
|
+
toast.textContent = message;
|
|
124
|
+
document.body.appendChild(toast);
|
|
125
|
+
return toast;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
customElements.define("glass-toast", GlassToast);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export declare class GlassToggle extends BaseElement {
|
|
3
|
+
static get observedAttributes(): string[];
|
|
4
|
+
connectedCallback(): void;
|
|
5
|
+
private updateThumb;
|
|
6
|
+
attributeChangedCallback(name: string): void;
|
|
7
|
+
get checked(): boolean;
|
|
8
|
+
set checked(value: boolean);
|
|
9
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
import { springAnimate, bouncySpring } from "../core/spring-engine.js";
|
|
3
|
+
export class GlassToggle extends BaseElement {
|
|
4
|
+
static get observedAttributes() {
|
|
5
|
+
return ["checked", "disabled"];
|
|
6
|
+
}
|
|
7
|
+
connectedCallback() {
|
|
8
|
+
this.mount(`
|
|
9
|
+
<button role="switch" aria-checked="false">
|
|
10
|
+
<span class="track">
|
|
11
|
+
<span class="thumb"></span>
|
|
12
|
+
</span>
|
|
13
|
+
<span class="label"><slot></slot></span>
|
|
14
|
+
</button>
|
|
15
|
+
`, `
|
|
16
|
+
:host {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
}
|
|
19
|
+
button {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 0.75rem;
|
|
23
|
+
background: none;
|
|
24
|
+
border: none;
|
|
25
|
+
color: inherit;
|
|
26
|
+
font: inherit;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
padding: 0;
|
|
29
|
+
}
|
|
30
|
+
button:focus-visible .track {
|
|
31
|
+
outline: 2px solid var(--lg-accent-focus, #5ac8fa);
|
|
32
|
+
outline-offset: 2px;
|
|
33
|
+
}
|
|
34
|
+
button:disabled {
|
|
35
|
+
opacity: 0.5;
|
|
36
|
+
cursor: not-allowed;
|
|
37
|
+
}
|
|
38
|
+
.track {
|
|
39
|
+
position: relative;
|
|
40
|
+
width: 52px;
|
|
41
|
+
height: 32px;
|
|
42
|
+
background: rgba(255, 255, 255, 0.15);
|
|
43
|
+
border-radius: 999px;
|
|
44
|
+
border: 1px solid var(--lg-border);
|
|
45
|
+
transition: background 0.3s ease;
|
|
46
|
+
}
|
|
47
|
+
.thumb {
|
|
48
|
+
position: absolute;
|
|
49
|
+
top: 3px;
|
|
50
|
+
left: 3px;
|
|
51
|
+
width: 24px;
|
|
52
|
+
height: 24px;
|
|
53
|
+
background: white;
|
|
54
|
+
border-radius: 50%;
|
|
55
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
56
|
+
}
|
|
57
|
+
:host([checked]) .track {
|
|
58
|
+
background: var(--lg-accent);
|
|
59
|
+
}
|
|
60
|
+
.label {
|
|
61
|
+
user-select: none;
|
|
62
|
+
}
|
|
63
|
+
`);
|
|
64
|
+
const button = this.root.querySelector("button");
|
|
65
|
+
const thumb = this.root.querySelector(".thumb");
|
|
66
|
+
button.addEventListener("click", () => {
|
|
67
|
+
if (this.hasAttribute("disabled"))
|
|
68
|
+
return;
|
|
69
|
+
this.toggleAttribute("checked");
|
|
70
|
+
});
|
|
71
|
+
// Spring animation for thumb
|
|
72
|
+
this.updateThumb(thumb, false);
|
|
73
|
+
}
|
|
74
|
+
updateThumb(thumb, animate = true) {
|
|
75
|
+
const checked = this.hasAttribute("checked");
|
|
76
|
+
const target = checked ? 21 : 3;
|
|
77
|
+
if (animate) {
|
|
78
|
+
const current = parseFloat(thumb.style.left) || 3;
|
|
79
|
+
springAnimate(current, target, v => {
|
|
80
|
+
thumb.style.left = `${v}px`;
|
|
81
|
+
}, bouncySpring);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
thumb.style.left = `${target}px`;
|
|
85
|
+
}
|
|
86
|
+
const button = this.root.querySelector("button");
|
|
87
|
+
button === null || button === void 0 ? void 0 : button.setAttribute("aria-checked", String(checked));
|
|
88
|
+
}
|
|
89
|
+
attributeChangedCallback(name) {
|
|
90
|
+
if (name === "checked") {
|
|
91
|
+
const thumb = this.root.querySelector(".thumb");
|
|
92
|
+
if (thumb)
|
|
93
|
+
this.updateThumb(thumb);
|
|
94
|
+
this.dispatchEvent(new CustomEvent("change", {
|
|
95
|
+
detail: { checked: this.hasAttribute("checked") },
|
|
96
|
+
bubbles: true
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
if (name === "disabled") {
|
|
100
|
+
const button = this.root.querySelector("button");
|
|
101
|
+
if (button)
|
|
102
|
+
button.disabled = this.hasAttribute("disabled");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
get checked() {
|
|
106
|
+
return this.hasAttribute("checked");
|
|
107
|
+
}
|
|
108
|
+
set checked(value) {
|
|
109
|
+
this.toggleAttribute("checked", value);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
customElements.define("glass-toggle", GlassToggle);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export declare class GlassTooltip extends BaseElement {
|
|
3
|
+
private tooltipEl?;
|
|
4
|
+
static get observedAttributes(): string[];
|
|
5
|
+
connectedCallback(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare class GlassPopover extends BaseElement {
|
|
8
|
+
private isOpen;
|
|
9
|
+
static get observedAttributes(): string[];
|
|
10
|
+
connectedCallback(): void;
|
|
11
|
+
toggle(): void;
|
|
12
|
+
open(): void;
|
|
13
|
+
close(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export class GlassTooltip extends BaseElement {
|
|
3
|
+
static get observedAttributes() {
|
|
4
|
+
return ["position", "delay"];
|
|
5
|
+
}
|
|
6
|
+
connectedCallback() {
|
|
7
|
+
const position = this.getAttribute("position") || "top";
|
|
8
|
+
this.mount(`
|
|
9
|
+
<div class="wrapper">
|
|
10
|
+
<slot></slot>
|
|
11
|
+
<div class="tooltip ${position}" role="tooltip">
|
|
12
|
+
<slot name="content"></slot>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
`, `
|
|
16
|
+
:host {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
position: relative;
|
|
19
|
+
}
|
|
20
|
+
.wrapper {
|
|
21
|
+
position: relative;
|
|
22
|
+
display: inline-block;
|
|
23
|
+
}
|
|
24
|
+
.tooltip {
|
|
25
|
+
position: absolute;
|
|
26
|
+
padding: 0.5rem 0.75rem;
|
|
27
|
+
background: var(--lg-bg);
|
|
28
|
+
backdrop-filter: blur(var(--lg-blur));
|
|
29
|
+
border: 1px solid var(--lg-border);
|
|
30
|
+
border-radius: calc(var(--lg-radius) / 2);
|
|
31
|
+
box-shadow: var(--lg-shadow-sm);
|
|
32
|
+
font-size: 0.875rem;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
opacity: 0;
|
|
35
|
+
visibility: hidden;
|
|
36
|
+
transform: scale(0.9);
|
|
37
|
+
transition: all 0.15s cubic-bezier(0.16, 1, 0.3, 1);
|
|
38
|
+
z-index: 10000;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
}
|
|
41
|
+
.wrapper:hover .tooltip,
|
|
42
|
+
.wrapper:focus-within .tooltip {
|
|
43
|
+
opacity: 1;
|
|
44
|
+
visibility: visible;
|
|
45
|
+
transform: scale(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Positions */
|
|
49
|
+
.tooltip.top {
|
|
50
|
+
bottom: calc(100% + 8px);
|
|
51
|
+
left: 50%;
|
|
52
|
+
transform-origin: bottom center;
|
|
53
|
+
}
|
|
54
|
+
.wrapper:hover .tooltip.top,
|
|
55
|
+
.wrapper:focus-within .tooltip.top {
|
|
56
|
+
transform: translateX(-50%) scale(1);
|
|
57
|
+
}
|
|
58
|
+
.tooltip.top:not(:hover) {
|
|
59
|
+
transform: translateX(-50%) scale(0.9);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.tooltip.bottom {
|
|
63
|
+
top: calc(100% + 8px);
|
|
64
|
+
left: 50%;
|
|
65
|
+
transform-origin: top center;
|
|
66
|
+
}
|
|
67
|
+
.wrapper:hover .tooltip.bottom,
|
|
68
|
+
.wrapper:focus-within .tooltip.bottom {
|
|
69
|
+
transform: translateX(-50%) scale(1);
|
|
70
|
+
}
|
|
71
|
+
.tooltip.bottom:not(:hover) {
|
|
72
|
+
transform: translateX(-50%) scale(0.9);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.tooltip.left {
|
|
76
|
+
right: calc(100% + 8px);
|
|
77
|
+
top: 50%;
|
|
78
|
+
transform-origin: right center;
|
|
79
|
+
}
|
|
80
|
+
.wrapper:hover .tooltip.left,
|
|
81
|
+
.wrapper:focus-within .tooltip.left {
|
|
82
|
+
transform: translateY(-50%) scale(1);
|
|
83
|
+
}
|
|
84
|
+
.tooltip.left:not(:hover) {
|
|
85
|
+
transform: translateY(-50%) scale(0.9);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.tooltip.right {
|
|
89
|
+
left: calc(100% + 8px);
|
|
90
|
+
top: 50%;
|
|
91
|
+
transform-origin: left center;
|
|
92
|
+
}
|
|
93
|
+
.wrapper:hover .tooltip.right,
|
|
94
|
+
.wrapper:focus-within .tooltip.right {
|
|
95
|
+
transform: translateY(-50%) scale(1);
|
|
96
|
+
}
|
|
97
|
+
.tooltip.right:not(:hover) {
|
|
98
|
+
transform: translateY(-50%) scale(0.9);
|
|
99
|
+
}
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
customElements.define("glass-tooltip", GlassTooltip);
|
|
104
|
+
// Popover with click trigger
|
|
105
|
+
export class GlassPopover extends BaseElement {
|
|
106
|
+
constructor() {
|
|
107
|
+
super(...arguments);
|
|
108
|
+
this.isOpen = false;
|
|
109
|
+
}
|
|
110
|
+
static get observedAttributes() {
|
|
111
|
+
return ["position", "open"];
|
|
112
|
+
}
|
|
113
|
+
connectedCallback() {
|
|
114
|
+
var _a;
|
|
115
|
+
const position = this.getAttribute("position") || "bottom";
|
|
116
|
+
this.mount(`
|
|
117
|
+
<div class="wrapper">
|
|
118
|
+
<div class="trigger">
|
|
119
|
+
<slot name="trigger"></slot>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="popover ${position}" role="dialog">
|
|
122
|
+
<slot></slot>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
`, `
|
|
126
|
+
:host {
|
|
127
|
+
display: inline-block;
|
|
128
|
+
position: relative;
|
|
129
|
+
}
|
|
130
|
+
.wrapper {
|
|
131
|
+
position: relative;
|
|
132
|
+
}
|
|
133
|
+
.trigger {
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
}
|
|
136
|
+
.popover {
|
|
137
|
+
position: absolute;
|
|
138
|
+
min-width: 200px;
|
|
139
|
+
padding: 1rem;
|
|
140
|
+
background: var(--lg-bg);
|
|
141
|
+
backdrop-filter: blur(var(--lg-blur));
|
|
142
|
+
border: 1px solid var(--lg-border);
|
|
143
|
+
border-radius: var(--lg-radius);
|
|
144
|
+
box-shadow: var(--lg-shadow);
|
|
145
|
+
opacity: 0;
|
|
146
|
+
visibility: hidden;
|
|
147
|
+
transform: scale(0.95);
|
|
148
|
+
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
149
|
+
z-index: 10000;
|
|
150
|
+
}
|
|
151
|
+
:host([open]) .popover {
|
|
152
|
+
opacity: 1;
|
|
153
|
+
visibility: visible;
|
|
154
|
+
transform: scale(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.popover.top {
|
|
158
|
+
bottom: calc(100% + 8px);
|
|
159
|
+
left: 50%;
|
|
160
|
+
transform-origin: bottom center;
|
|
161
|
+
}
|
|
162
|
+
:host([open]) .popover.top { transform: translateX(-50%) scale(1); }
|
|
163
|
+
.popover.top { transform: translateX(-50%) scale(0.95); }
|
|
164
|
+
|
|
165
|
+
.popover.bottom {
|
|
166
|
+
top: calc(100% + 8px);
|
|
167
|
+
left: 50%;
|
|
168
|
+
transform-origin: top center;
|
|
169
|
+
}
|
|
170
|
+
:host([open]) .popover.bottom { transform: translateX(-50%) scale(1); }
|
|
171
|
+
.popover.bottom { transform: translateX(-50%) scale(0.95); }
|
|
172
|
+
|
|
173
|
+
.popover.left {
|
|
174
|
+
right: calc(100% + 8px);
|
|
175
|
+
top: 50%;
|
|
176
|
+
transform-origin: right center;
|
|
177
|
+
}
|
|
178
|
+
:host([open]) .popover.left { transform: translateY(-50%) scale(1); }
|
|
179
|
+
.popover.left { transform: translateY(-50%) scale(0.95); }
|
|
180
|
+
|
|
181
|
+
.popover.right {
|
|
182
|
+
left: calc(100% + 8px);
|
|
183
|
+
top: 50%;
|
|
184
|
+
transform-origin: left center;
|
|
185
|
+
}
|
|
186
|
+
:host([open]) .popover.right { transform: translateY(-50%) scale(1); }
|
|
187
|
+
.popover.right { transform: translateY(-50%) scale(0.95); }
|
|
188
|
+
`);
|
|
189
|
+
(_a = this.root.querySelector(".trigger")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => this.toggle());
|
|
190
|
+
// Close on outside click
|
|
191
|
+
document.addEventListener("click", (e) => {
|
|
192
|
+
if (!this.contains(e.target) && this.isOpen) {
|
|
193
|
+
this.close();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
// Close on Escape
|
|
197
|
+
this.addEventListener("keydown", (e) => {
|
|
198
|
+
if (e.key === "Escape")
|
|
199
|
+
this.close();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
toggle() {
|
|
203
|
+
this.isOpen ? this.close() : this.open();
|
|
204
|
+
}
|
|
205
|
+
open() {
|
|
206
|
+
this.isOpen = true;
|
|
207
|
+
this.setAttribute("open", "");
|
|
208
|
+
}
|
|
209
|
+
close() {
|
|
210
|
+
this.isOpen = false;
|
|
211
|
+
this.removeAttribute("open");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
customElements.define("glass-popover", GlassPopover);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const liquidCurves: {
|
|
2
|
+
standard: string;
|
|
3
|
+
emphasized: string;
|
|
4
|
+
decelerate: string;
|
|
5
|
+
accelerate: string;
|
|
6
|
+
glassIn: string;
|
|
7
|
+
glassOut: string;
|
|
8
|
+
glassInOut: string;
|
|
9
|
+
elasticOut: string;
|
|
10
|
+
bounceOut: string;
|
|
11
|
+
smooth: string;
|
|
12
|
+
smoothIn: string;
|
|
13
|
+
smoothOut: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const durations: {
|
|
16
|
+
instant: number;
|
|
17
|
+
fast: number;
|
|
18
|
+
normal: number;
|
|
19
|
+
slow: number;
|
|
20
|
+
reveal: number;
|
|
21
|
+
};
|
|
22
|
+
export declare function createTransition(properties: string | string[], duration?: number, curve?: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Easing curves
|
|
2
|
+
export const liquidCurves = {
|
|
3
|
+
// Standard curves
|
|
4
|
+
standard: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
5
|
+
emphasized: "cubic-bezier(0.2, 0, 0, 1)",
|
|
6
|
+
decelerate: "cubic-bezier(0, 0, 0.2, 1)",
|
|
7
|
+
accelerate: "cubic-bezier(0.4, 0, 1, 1)",
|
|
8
|
+
// Glass curves
|
|
9
|
+
glassIn: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
10
|
+
glassOut: "cubic-bezier(0.7, 0, 0.84, 0)",
|
|
11
|
+
glassInOut: "cubic-bezier(0.87, 0, 0.13, 1)",
|
|
12
|
+
// Elastic curves
|
|
13
|
+
elasticOut: "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
14
|
+
bounceOut: "cubic-bezier(0.34, 1.3, 0.64, 1)",
|
|
15
|
+
// Smooth
|
|
16
|
+
smooth: "cubic-bezier(0.25, 0.1, 0.25, 1)",
|
|
17
|
+
smoothIn: "cubic-bezier(0.42, 0, 1, 1)",
|
|
18
|
+
smoothOut: "cubic-bezier(0, 0, 0.58, 1)",
|
|
19
|
+
};
|
|
20
|
+
// Duration presets
|
|
21
|
+
export const durations = {
|
|
22
|
+
instant: 100,
|
|
23
|
+
fast: 150,
|
|
24
|
+
normal: 250,
|
|
25
|
+
slow: 400,
|
|
26
|
+
reveal: 600,
|
|
27
|
+
};
|
|
28
|
+
// Helper to create transition strings
|
|
29
|
+
export function createTransition(properties, duration = durations.normal, curve = liquidCurves.standard) {
|
|
30
|
+
const props = Array.isArray(properties) ? properties : [properties];
|
|
31
|
+
return props.map(p => `${p} ${duration}ms ${curve}`).join(", ");
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function trapFocus(el: HTMLElement): () => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function trapFocus(el) {
|
|
2
|
+
const items = el.querySelectorAll("button,input,[tabindex]:not([tabindex='-1'])");
|
|
3
|
+
const first = items[0], last = items[items.length - 1];
|
|
4
|
+
function handler(e) {
|
|
5
|
+
if (e.key !== "Tab")
|
|
6
|
+
return;
|
|
7
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
8
|
+
e.preventDefault();
|
|
9
|
+
last.focus();
|
|
10
|
+
}
|
|
11
|
+
else if (!e.shiftKey && document.activeElement === last) {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
first.focus();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
el.addEventListener("keydown", handler);
|
|
17
|
+
first === null || first === void 0 ? void 0 : first.focus();
|
|
18
|
+
return () => el.removeEventListener("keydown", handler);
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Glow effect CSS
|
|
2
|
+
export const glowCSS = (accent) => `
|
|
3
|
+
position: absolute;
|
|
4
|
+
inset: -2px;
|
|
5
|
+
background: ${accent};
|
|
6
|
+
filter: blur(12px);
|
|
7
|
+
opacity: 0;
|
|
8
|
+
transition: opacity 0.3s ease;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
z-index: -1;
|
|
11
|
+
border-radius: inherit;
|
|
12
|
+
`;
|
|
13
|
+
// Ripple effect
|
|
14
|
+
export function createRipple(event, element, color = "rgba(255, 255, 255, 0.4)") {
|
|
15
|
+
const rect = element.getBoundingClientRect();
|
|
16
|
+
const size = Math.max(rect.width, rect.height) * 2;
|
|
17
|
+
const x = event.clientX - rect.left - size / 2;
|
|
18
|
+
const y = event.clientY - rect.top - size / 2;
|
|
19
|
+
const ripple = document.createElement("span");
|
|
20
|
+
ripple.style.cssText = `
|
|
21
|
+
position: absolute;
|
|
22
|
+
width: ${size}px;
|
|
23
|
+
height: ${size}px;
|
|
24
|
+
left: ${x}px;
|
|
25
|
+
top: ${y}px;
|
|
26
|
+
background: ${color};
|
|
27
|
+
border-radius: 50%;
|
|
28
|
+
transform: scale(0);
|
|
29
|
+
opacity: 1;
|
|
30
|
+
pointer-events: none;
|
|
31
|
+
animation: ripple-expand 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
32
|
+
`;
|
|
33
|
+
element.style.position = "relative";
|
|
34
|
+
element.style.overflow = "hidden";
|
|
35
|
+
element.appendChild(ripple);
|
|
36
|
+
ripple.addEventListener("animationend", () => ripple.remove());
|
|
37
|
+
}
|
|
38
|
+
// Inject ripple keyframes
|
|
39
|
+
export function injectRippleStyles() {
|
|
40
|
+
if (document.getElementById("lg-ripple-styles"))
|
|
41
|
+
return;
|
|
42
|
+
const style = document.createElement("style");
|
|
43
|
+
style.id = "lg-ripple-styles";
|
|
44
|
+
style.textContent = `
|
|
45
|
+
@keyframes ripple-expand {
|
|
46
|
+
to {
|
|
47
|
+
transform: scale(1);
|
|
48
|
+
opacity: 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
document.head.appendChild(style);
|
|
53
|
+
}
|
|
54
|
+
// Init on load
|
|
55
|
+
if (typeof document !== "undefined") {
|
|
56
|
+
injectRippleStyles();
|
|
57
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const motion: {
|
|
2
|
+
animate(element: HTMLElement, keyframes: Keyframe[], options: KeyframeAnimationOptions): Animation;
|
|
3
|
+
fadeIn(element: HTMLElement, duration?: number): Animation;
|
|
4
|
+
fadeOut(element: HTMLElement, duration?: number): Animation;
|
|
5
|
+
scaleIn(element: HTMLElement, duration?: number): Animation;
|
|
6
|
+
scaleOut(element: HTMLElement, duration?: number): Animation;
|
|
7
|
+
slideInUp(element: HTMLElement, duration?: number): Animation;
|
|
8
|
+
slideOutDown(element: HTMLElement, duration?: number): Animation;
|
|
9
|
+
blurIn(element: HTMLElement, duration?: number): Animation;
|
|
10
|
+
shimmer(element: HTMLElement): Animation;
|
|
11
|
+
pulseGlow(element: HTMLElement): Animation;
|
|
12
|
+
};
|