@birdapi/velinstyle 0.7.0 → 0.8.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.de.md +26 -4
- package/README.md +26 -4
- package/cli/blueprint.js +8 -0
- package/cli/blueprints/bottom-nav-mobile.html +17 -0
- package/cli/blueprints/cookie-consent.html +9 -0
- package/cli/blueprints/empty-state.html +5 -0
- package/cli/blueprints/filter-bar.html +15 -0
- package/cli/blueprints/notification-center.html +13 -0
- package/cli/blueprints/onboarding.html +23 -0
- package/cli/blueprints/pricing-table.html +20 -0
- package/cli/blueprints/settings-panel.html +20 -0
- package/cli/index.js +116 -1
- package/cli/layout-audit.js +325 -0
- package/cli/scaffold-recipes.json +70 -0
- package/cli/scaffold.js +155 -0
- package/cli/scanner.js +68 -0
- package/components/index.js +19 -0
- package/components/sanitize.js +29 -3
- package/components/shadow-a11y-styles.js +18 -0
- package/components/velin-announcer.js +35 -0
- package/components/velin-bottom-nav.js +89 -0
- package/components/velin-combobox.js +149 -0
- package/components/velin-command.js +127 -0
- package/components/velin-counter.js +152 -0
- package/components/velin-flip.js +220 -0
- package/components/velin-icon.js +43 -9
- package/components/velin-live-dot.js +85 -0
- package/components/velin-menubar.js +83 -0
- package/components/velin-rating.js +91 -0
- package/components/velin-reveal.js +80 -0
- package/components/velin-segmented-control.js +108 -0
- package/components/velin-sheet.js +107 -0
- package/components/velin-sparkline.js +207 -0
- package/components/velin-theme-toggle.js +277 -60
- package/dist/velinstyle-components.iife.js +1629 -69
- package/dist/velinstyle-components.js +1649 -69
- package/dist/velinstyle-components.min.js +424 -80
- package/dist/velinstyle.css +472 -3
- package/dist/velinstyle.min.css +1 -1
- package/package.json +3 -2
- package/src/a11y/security.css +18 -0
- package/src/base/reset.css +12 -1
- package/src/components/nav.css +152 -151
- package/src/tokens/motion.css +7 -0
- package/src/utilities/animation.css +97 -1
- package/src/utilities/chart-animation.css +101 -0
- package/src/utilities/filter-effects.css +103 -0
- package/src/utilities/safe-area.css +39 -0
- package/src/velinstyle.css +3 -0
|
@@ -94,20 +94,50 @@ function clearBackgroundInert() {
|
|
|
94
94
|
// components/sanitize.js
|
|
95
95
|
var ESCAPE_MAP = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
96
96
|
var ESCAPE_RE = /[&<>"']/g;
|
|
97
|
+
var CONTROL_RE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
|
|
98
|
+
var ALLOWED_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "data:", "mailto:", "tel:"]);
|
|
99
|
+
var BLOCKED_DATA_MIME = /^data:text\/html/i;
|
|
97
100
|
function escapeHTML(str) {
|
|
98
101
|
if (typeof str !== "string") return "";
|
|
99
102
|
return str.replace(ESCAPE_RE, (ch) => ESCAPE_MAP[ch]);
|
|
100
103
|
}
|
|
104
|
+
function stripControlChars(str) {
|
|
105
|
+
if (typeof str !== "string") return "";
|
|
106
|
+
return str.replace(CONTROL_RE, "");
|
|
107
|
+
}
|
|
108
|
+
function escapeHTMLAttribute(str) {
|
|
109
|
+
return escapeHTML(stripControlChars(str));
|
|
110
|
+
}
|
|
101
111
|
function sanitizeURL(url) {
|
|
102
112
|
if (typeof url !== "string") return "";
|
|
113
|
+
const trimmed = url.trim();
|
|
114
|
+
if (/^\s*javascript:/i.test(trimmed) || /^\s*vbscript:/i.test(trimmed)) return "";
|
|
115
|
+
if (BLOCKED_DATA_MIME.test(trimmed)) return "";
|
|
103
116
|
try {
|
|
104
|
-
const parsed = new URL(
|
|
105
|
-
if (
|
|
106
|
-
return "";
|
|
117
|
+
const parsed = new URL(trimmed, typeof location !== "undefined" ? location.href : "https://example.invalid/");
|
|
118
|
+
if (!ALLOWED_URL_PROTOCOLS.has(parsed.protocol)) return "";
|
|
119
|
+
if (parsed.protocol === "data:" && BLOCKED_DATA_MIME.test(trimmed)) return "";
|
|
120
|
+
return trimmed;
|
|
107
121
|
} catch {
|
|
108
122
|
return "";
|
|
109
123
|
}
|
|
110
124
|
}
|
|
125
|
+
var _policy = null;
|
|
126
|
+
function getTrustedPolicy() {
|
|
127
|
+
if (_policy) return _policy;
|
|
128
|
+
if (typeof window !== "undefined" && window.trustedTypes?.createPolicy) {
|
|
129
|
+
_policy = window.trustedTypes.createPolicy("velinstyle", {
|
|
130
|
+
createHTML: (input) => escapeHTML(input)
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return _policy;
|
|
134
|
+
}
|
|
135
|
+
function createSafeHTML(str) {
|
|
136
|
+
const policy = getTrustedPolicy();
|
|
137
|
+
const safe = escapeHTML(stripControlChars(str));
|
|
138
|
+
if (policy?.createHTML) return policy.createHTML(safe);
|
|
139
|
+
return safe;
|
|
140
|
+
}
|
|
111
141
|
|
|
112
142
|
// components/velin-modal.js
|
|
113
143
|
var styles = `
|
|
@@ -781,12 +811,29 @@ var PROVIDER_CDNS = {
|
|
|
781
811
|
heroicons: "https://unpkg.com/heroicons@2/24/outline/{name}.svg",
|
|
782
812
|
bootstrap: "https://unpkg.com/bootstrap-icons@latest/icons/{name}.svg",
|
|
783
813
|
material: "https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/{name}/default/24px.svg",
|
|
784
|
-
fontawesome: "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/
|
|
814
|
+
fontawesome: "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/solid/{name}.svg"
|
|
785
815
|
};
|
|
816
|
+
var PROVIDER_VARIANTS = {
|
|
817
|
+
fontawesome: {
|
|
818
|
+
regular: "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/regular/{name}.svg",
|
|
819
|
+
solid: "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/solid/{name}.svg",
|
|
820
|
+
brands: "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/brands/{name}.svg"
|
|
821
|
+
},
|
|
822
|
+
heroicons: {
|
|
823
|
+
outline: "https://unpkg.com/heroicons@2/24/outline/{name}.svg",
|
|
824
|
+
solid: "https://unpkg.com/heroicons@2/24/solid/{name}.svg",
|
|
825
|
+
mini: "https://unpkg.com/heroicons@2/20/solid/{name}.svg"
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
function resolveProviderUrl(provider, variant) {
|
|
829
|
+
const variants = PROVIDER_VARIANTS[provider];
|
|
830
|
+
if (variant && variants?.[variant]) return variants[variant];
|
|
831
|
+
return PROVIDER_CDNS[provider];
|
|
832
|
+
}
|
|
786
833
|
var _svgCache = /* @__PURE__ */ new Map();
|
|
787
834
|
var VelinIcon = class extends HTMLElement {
|
|
788
835
|
static get observedAttributes() {
|
|
789
|
-
return ["name", "size", "label", "provider", "sprite"];
|
|
836
|
+
return ["name", "size", "label", "provider", "variant", "sprite"];
|
|
790
837
|
}
|
|
791
838
|
constructor() {
|
|
792
839
|
super();
|
|
@@ -803,12 +850,13 @@ var VelinIcon = class extends HTMLElement {
|
|
|
803
850
|
const size = this.getAttribute("size") || "24";
|
|
804
851
|
const label = this.getAttribute("label");
|
|
805
852
|
const provider = this.getAttribute("provider");
|
|
853
|
+
const variant = this.getAttribute("variant");
|
|
806
854
|
if (!name) {
|
|
807
855
|
this.innerHTML = "";
|
|
808
856
|
return;
|
|
809
857
|
}
|
|
810
|
-
if (provider && PROVIDER_CDNS[provider]) {
|
|
811
|
-
this._renderFromCDN(name, size, label, provider);
|
|
858
|
+
if (provider && (PROVIDER_CDNS[provider] || PROVIDER_VARIANTS[provider])) {
|
|
859
|
+
this._renderFromCDN(name, size, label, provider, variant);
|
|
812
860
|
return;
|
|
813
861
|
}
|
|
814
862
|
this._renderFromSprite(name, size, label);
|
|
@@ -827,20 +875,34 @@ var VelinIcon = class extends HTMLElement {
|
|
|
827
875
|
this._applyStyle(svg);
|
|
828
876
|
this._applyA11y(svg, label);
|
|
829
877
|
const use = document.createElementNS(svgNS, "use");
|
|
830
|
-
const
|
|
831
|
-
|
|
878
|
+
const spriteAttr = this.getAttribute("sprite");
|
|
879
|
+
const localSymbol = document.getElementById(name);
|
|
880
|
+
const isLocalSymbol = localSymbol && localSymbol.tagName && localSymbol.tagName.toLowerCase() === "symbol";
|
|
881
|
+
let href;
|
|
882
|
+
if (spriteAttr === "" || spriteAttr == null && isLocalSymbol) {
|
|
883
|
+
href = `#${name}`;
|
|
884
|
+
} else {
|
|
885
|
+
const spriteUrl = spriteAttr || "velin-icons.svg";
|
|
886
|
+
href = `${spriteUrl}#${name}`;
|
|
887
|
+
}
|
|
888
|
+
use.setAttribute("href", href);
|
|
832
889
|
svg.appendChild(use);
|
|
833
890
|
this.innerHTML = "";
|
|
834
891
|
this.appendChild(svg);
|
|
835
892
|
this._rendered = true;
|
|
836
893
|
}
|
|
837
|
-
async _renderFromCDN(name, size, label, provider) {
|
|
838
|
-
const cacheKey = `${provider}:${name}`;
|
|
894
|
+
async _renderFromCDN(name, size, label, provider, variant) {
|
|
895
|
+
const cacheKey = `${provider}:${variant || "default"}:${name}`;
|
|
839
896
|
if (_svgCache.has(cacheKey)) {
|
|
840
897
|
this._injectSVG(_svgCache.get(cacheKey), size, label);
|
|
841
898
|
return;
|
|
842
899
|
}
|
|
843
|
-
const
|
|
900
|
+
const template = resolveProviderUrl(provider, variant);
|
|
901
|
+
if (!template) {
|
|
902
|
+
this._renderFromSprite(name, size, label);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const url = template.replace("{name}", name);
|
|
844
906
|
try {
|
|
845
907
|
const res = await fetch(url);
|
|
846
908
|
if (!res.ok) throw new Error(`${res.status}`);
|
|
@@ -1009,86 +1071,284 @@ customElements.define("velin-drawer", VelinDrawer);
|
|
|
1009
1071
|
var velin_drawer_default = VelinDrawer;
|
|
1010
1072
|
|
|
1011
1073
|
// components/velin-theme-toggle.js
|
|
1074
|
+
var THEMES = [
|
|
1075
|
+
{ slug: "", label: "Default (Light)" },
|
|
1076
|
+
{ slug: "dark", label: "Dark" },
|
|
1077
|
+
{ slug: "brutalist", label: "Brutalist" },
|
|
1078
|
+
{ slug: "corporate", label: "Corporate" },
|
|
1079
|
+
{ slug: "earth", label: "Earth" },
|
|
1080
|
+
{ slug: "forest", label: "Forest" },
|
|
1081
|
+
{ slug: "midnight", label: "Midnight" },
|
|
1082
|
+
{ slug: "neon", label: "Neon" },
|
|
1083
|
+
{ slug: "nordic", label: "Nordic" },
|
|
1084
|
+
{ slug: "ocean", label: "Ocean" },
|
|
1085
|
+
{ slug: "pastel", label: "Pastel" },
|
|
1086
|
+
{ slug: "retro", label: "Retro" },
|
|
1087
|
+
{ slug: "sharp", label: "Sharp" },
|
|
1088
|
+
{ slug: "soft", label: "Soft" },
|
|
1089
|
+
{ slug: "sunset", label: "Sunset" }
|
|
1090
|
+
];
|
|
1091
|
+
var BUILTIN_THEMES = /* @__PURE__ */ new Set(["", "dark"]);
|
|
1092
|
+
var loadedThemeStylesheets = /* @__PURE__ */ new Set();
|
|
1093
|
+
function ensureThemeStylesheet(slug, base) {
|
|
1094
|
+
if (!slug || BUILTIN_THEMES.has(slug)) return;
|
|
1095
|
+
if (loadedThemeStylesheets.has(slug)) return;
|
|
1096
|
+
const existing = document.querySelector(`link[data-velin-theme-css="${slug}"]`);
|
|
1097
|
+
if (existing) {
|
|
1098
|
+
loadedThemeStylesheets.add(slug);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const link = document.createElement("link");
|
|
1102
|
+
link.rel = "stylesheet";
|
|
1103
|
+
link.href = `${base.replace(/\/$/, "")}/${slug}.min.css`;
|
|
1104
|
+
link.setAttribute("data-velin-theme-css", slug);
|
|
1105
|
+
document.head.appendChild(link);
|
|
1106
|
+
loadedThemeStylesheets.add(slug);
|
|
1107
|
+
}
|
|
1012
1108
|
var styles7 = `
|
|
1013
|
-
:host { display: inline-flex; }
|
|
1109
|
+
:host { display: inline-flex; position: relative; }
|
|
1110
|
+
.group {
|
|
1111
|
+
display: inline-flex; align-items: stretch;
|
|
1112
|
+
border: 2px solid var(--velin-color-border, #ddd);
|
|
1113
|
+
border-radius: var(--velin-radius-md, 0.5rem);
|
|
1114
|
+
background: none;
|
|
1115
|
+
overflow: hidden;
|
|
1116
|
+
}
|
|
1014
1117
|
button {
|
|
1015
1118
|
display: inline-flex; align-items: center; justify-content: center;
|
|
1016
|
-
min-
|
|
1017
|
-
background: none; border:
|
|
1018
|
-
|
|
1019
|
-
|
|
1119
|
+
min-height: 2.75rem; padding: 0.5rem;
|
|
1120
|
+
background: none; border: 0; cursor: pointer;
|
|
1121
|
+
color: var(--velin-color-text, #111);
|
|
1122
|
+
transition: background 150ms ease;
|
|
1123
|
+
}
|
|
1124
|
+
button:hover { background: var(--velin-color-surface-dim, #eee); }
|
|
1125
|
+
button:focus-visible {
|
|
1126
|
+
outline: 3px solid var(--velin-color-focus, #2563eb);
|
|
1127
|
+
outline-offset: 2px;
|
|
1128
|
+
}
|
|
1129
|
+
.toggle { min-width: 2.75rem; }
|
|
1130
|
+
.picker {
|
|
1131
|
+
min-width: 1.75rem;
|
|
1132
|
+
border-inline-start: 1px solid var(--velin-color-border, #ddd);
|
|
1133
|
+
color: var(--velin-color-text-muted, #555);
|
|
1020
1134
|
}
|
|
1021
|
-
button:hover { background: var(--velin-color-surface-dim, #eee); border-color: var(--velin-color-border-strong, #999); }
|
|
1022
|
-
button:focus-visible { outline: 3px solid var(--velin-color-focus, #2563eb); outline-offset: 2px; }
|
|
1023
1135
|
svg { width: 1.25rem; height: 1.25rem; transition: transform 300ms ease; }
|
|
1136
|
+
.chev { width: 0.75rem; height: 0.75rem; }
|
|
1024
1137
|
:host([theme="dark"]) .sun { display: none; }
|
|
1025
1138
|
:host(:not([theme="dark"])) .moon { display: none; }
|
|
1139
|
+
:host([compact]) .picker { display: none; }
|
|
1140
|
+
:host([compact]) .toggle { border-inline-end: 0; }
|
|
1026
1141
|
@media (prefers-reduced-motion: reduce) { svg { transition: none; } }
|
|
1142
|
+
|
|
1143
|
+
.menu {
|
|
1144
|
+
position: absolute;
|
|
1145
|
+
top: calc(100% + 0.5rem);
|
|
1146
|
+
inset-inline-end: 0;
|
|
1147
|
+
z-index: 1000;
|
|
1148
|
+
min-width: 12rem;
|
|
1149
|
+
padding: 0.375rem;
|
|
1150
|
+
background: var(--velin-color-surface-bright, #fff);
|
|
1151
|
+
border: 1px solid var(--velin-color-border, #ddd);
|
|
1152
|
+
border-radius: var(--velin-radius-md, 0.5rem);
|
|
1153
|
+
box-shadow: var(--velin-shadow-lg, 0 12px 32px rgba(0,0,0,0.12));
|
|
1154
|
+
list-style: none;
|
|
1155
|
+
margin: 0;
|
|
1156
|
+
display: none;
|
|
1157
|
+
max-height: min(70vh, 24rem);
|
|
1158
|
+
overflow-y: auto;
|
|
1159
|
+
}
|
|
1160
|
+
:host([menu-open]) .menu { display: block; }
|
|
1161
|
+
.menu li { margin: 0; }
|
|
1162
|
+
.menu button {
|
|
1163
|
+
width: 100%;
|
|
1164
|
+
justify-content: flex-start;
|
|
1165
|
+
padding: 0.4rem 0.75rem;
|
|
1166
|
+
font-size: 0.875rem;
|
|
1167
|
+
color: var(--velin-color-text, #111);
|
|
1168
|
+
border-radius: var(--velin-radius-sm, 0.25rem);
|
|
1169
|
+
min-height: 2rem;
|
|
1170
|
+
text-align: start;
|
|
1171
|
+
}
|
|
1172
|
+
.menu button:hover,
|
|
1173
|
+
.menu button[aria-current="true"] {
|
|
1174
|
+
background: var(--velin-color-primary-subtle, #eef);
|
|
1175
|
+
color: var(--velin-color-primary, #2a4cf0);
|
|
1176
|
+
}
|
|
1177
|
+
.menu button[aria-current="true"] {
|
|
1178
|
+
font-weight: 600;
|
|
1179
|
+
}
|
|
1180
|
+
.menu .swatch {
|
|
1181
|
+
width: 0.75rem; height: 0.75rem;
|
|
1182
|
+
border-radius: 50%;
|
|
1183
|
+
margin-inline-end: 0.5rem;
|
|
1184
|
+
background: currentColor;
|
|
1185
|
+
border: 1px solid var(--velin-color-border, #ddd);
|
|
1186
|
+
}
|
|
1027
1187
|
`;
|
|
1028
1188
|
var VelinThemeToggle = class extends HTMLElement {
|
|
1029
1189
|
constructor() {
|
|
1030
1190
|
super();
|
|
1031
1191
|
this.attachShadow({ mode: "open" });
|
|
1192
|
+
this._onDocClick = this._onDocClick.bind(this);
|
|
1193
|
+
this._onKeyDown = this._onKeyDown.bind(this);
|
|
1032
1194
|
}
|
|
1033
1195
|
connectedCallback() {
|
|
1034
1196
|
this.shadowRoot.innerHTML = `
|
|
1035
1197
|
<style>${styles7}</style>
|
|
1036
|
-
<
|
|
1037
|
-
<
|
|
1038
|
-
<
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
<
|
|
1045
|
-
|
|
1046
|
-
|
|
1198
|
+
<div class="group" part="group">
|
|
1199
|
+
<button class="toggle" part="button" aria-label="Toggle dark mode">
|
|
1200
|
+
<svg class="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
1201
|
+
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/>
|
|
1202
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
1203
|
+
<line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/>
|
|
1204
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
1205
|
+
</svg>
|
|
1206
|
+
<svg class="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
1207
|
+
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
|
|
1208
|
+
</svg>
|
|
1209
|
+
</button>
|
|
1210
|
+
<button class="picker" part="picker" aria-label="Choose theme" aria-haspopup="menu" aria-expanded="false">
|
|
1211
|
+
<svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1212
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
1213
|
+
</svg>
|
|
1214
|
+
</button>
|
|
1215
|
+
</div>
|
|
1216
|
+
<ul class="menu" role="menu" hidden></ul>
|
|
1047
1217
|
`;
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1218
|
+
this._target = document.querySelector(this.getAttribute("target") || "html");
|
|
1219
|
+
this._themesBase = this.getAttribute("themes-base") || "dist/themes";
|
|
1220
|
+
this._menu = this.shadowRoot.querySelector(".menu");
|
|
1221
|
+
this._toggleBtn = this.shadowRoot.querySelector(".toggle");
|
|
1222
|
+
this._pickerBtn = this.shadowRoot.querySelector(".picker");
|
|
1223
|
+
this._renderMenu();
|
|
1224
|
+
this._initPreference();
|
|
1225
|
+
this._toggleBtn.addEventListener("click", () => this._toggleDarkMode());
|
|
1226
|
+
this._pickerBtn.addEventListener("click", (e) => {
|
|
1227
|
+
e.stopPropagation();
|
|
1228
|
+
this._toggleMenu();
|
|
1229
|
+
});
|
|
1230
|
+
document.addEventListener("click", this._onDocClick);
|
|
1231
|
+
this.shadowRoot.addEventListener("keydown", this._onKeyDown);
|
|
1051
1232
|
const prefersDarkMq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
1052
|
-
|
|
1053
|
-
if (localStorage.getItem("velin-theme")) return;
|
|
1054
|
-
if (prefersDarkMq.matches) {
|
|
1055
|
-
el?.setAttribute("data-velin-theme", "dark");
|
|
1056
|
-
this.setAttribute("theme", "dark");
|
|
1057
|
-
} else {
|
|
1058
|
-
el?.removeAttribute("data-velin-theme");
|
|
1059
|
-
this.removeAttribute("theme");
|
|
1060
|
-
}
|
|
1061
|
-
};
|
|
1062
|
-
const prefersDark = prefersDarkMq.matches;
|
|
1063
|
-
if (stored === "dark" || !stored && prefersDark) {
|
|
1064
|
-
el?.setAttribute("data-velin-theme", "dark");
|
|
1065
|
-
this.setAttribute("theme", "dark");
|
|
1066
|
-
}
|
|
1067
|
-
prefersDarkMq.addEventListener("change", applyFromPreference);
|
|
1233
|
+
prefersDarkMq.addEventListener("change", () => this._applyFromPreference());
|
|
1068
1234
|
window.addEventListener("storage", (e) => {
|
|
1069
|
-
if (e.key
|
|
1070
|
-
if (e.newValue === "dark") {
|
|
1071
|
-
el.setAttribute("data-velin-theme", "dark");
|
|
1072
|
-
this.setAttribute("theme", "dark");
|
|
1073
|
-
} else {
|
|
1074
|
-
el.removeAttribute("data-velin-theme");
|
|
1075
|
-
this.removeAttribute("theme");
|
|
1076
|
-
}
|
|
1235
|
+
if (e.key === "velin-theme") this._readStorage();
|
|
1077
1236
|
});
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1237
|
+
}
|
|
1238
|
+
disconnectedCallback() {
|
|
1239
|
+
document.removeEventListener("click", this._onDocClick);
|
|
1240
|
+
}
|
|
1241
|
+
_renderMenu() {
|
|
1242
|
+
this._menu.innerHTML = THEMES.map((t) => `
|
|
1243
|
+
<li role="none">
|
|
1244
|
+
<button type="button" role="menuitem" data-theme="${t.slug}">
|
|
1245
|
+
<span class="swatch" aria-hidden="true" data-theme-swatch="${t.slug}"></span>
|
|
1246
|
+
${t.label}
|
|
1247
|
+
</button>
|
|
1248
|
+
</li>
|
|
1249
|
+
`).join("");
|
|
1250
|
+
this._menu.removeAttribute("hidden");
|
|
1251
|
+
this._menu.querySelectorAll("button[data-theme]").forEach((btn) => {
|
|
1252
|
+
btn.addEventListener("click", () => {
|
|
1253
|
+
const slug = btn.getAttribute("data-theme");
|
|
1254
|
+
this._applyTheme(slug, { persist: true });
|
|
1255
|
+
this._closeMenu();
|
|
1256
|
+
});
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
_toggleMenu() {
|
|
1260
|
+
if (this.hasAttribute("menu-open")) this._closeMenu();
|
|
1261
|
+
else this._openMenu();
|
|
1262
|
+
}
|
|
1263
|
+
_openMenu() {
|
|
1264
|
+
this.setAttribute("menu-open", "");
|
|
1265
|
+
this._pickerBtn.setAttribute("aria-expanded", "true");
|
|
1266
|
+
this._highlightActive();
|
|
1267
|
+
const first = this._menu.querySelector("button[data-theme]");
|
|
1268
|
+
if (first) first.focus();
|
|
1269
|
+
}
|
|
1270
|
+
_closeMenu() {
|
|
1271
|
+
this.removeAttribute("menu-open");
|
|
1272
|
+
this._pickerBtn.setAttribute("aria-expanded", "false");
|
|
1273
|
+
}
|
|
1274
|
+
_onDocClick(e) {
|
|
1275
|
+
if (!this.hasAttribute("menu-open")) return;
|
|
1276
|
+
if (e.composedPath().includes(this)) return;
|
|
1277
|
+
this._closeMenu();
|
|
1278
|
+
}
|
|
1279
|
+
_onKeyDown(e) {
|
|
1280
|
+
if (!this.hasAttribute("menu-open")) return;
|
|
1281
|
+
if (e.key === "Escape") {
|
|
1282
|
+
e.preventDefault();
|
|
1283
|
+
this._closeMenu();
|
|
1284
|
+
this._pickerBtn.focus();
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
1288
|
+
e.preventDefault();
|
|
1289
|
+
const items = Array.from(this._menu.querySelectorAll("button[data-theme]"));
|
|
1290
|
+
const idx = items.indexOf(this.shadowRoot.activeElement);
|
|
1291
|
+
const next = e.key === "ArrowDown" ? items[(idx + 1) % items.length] : items[(idx - 1 + items.length) % items.length];
|
|
1292
|
+
next?.focus();
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
_highlightActive() {
|
|
1296
|
+
const current = this._currentSlug();
|
|
1297
|
+
this._menu.querySelectorAll("button[data-theme]").forEach((btn) => {
|
|
1298
|
+
const slug = btn.getAttribute("data-theme");
|
|
1299
|
+
if (slug === current) btn.setAttribute("aria-current", "true");
|
|
1300
|
+
else btn.removeAttribute("aria-current");
|
|
1090
1301
|
});
|
|
1091
1302
|
}
|
|
1303
|
+
_currentSlug() {
|
|
1304
|
+
if (!this._target) return "";
|
|
1305
|
+
const value = this._target.getAttribute("data-velin-theme");
|
|
1306
|
+
if (!value || value === "light") return "";
|
|
1307
|
+
return value;
|
|
1308
|
+
}
|
|
1309
|
+
_initPreference() {
|
|
1310
|
+
const stored = localStorage.getItem("velin-theme");
|
|
1311
|
+
if (stored) {
|
|
1312
|
+
this._applyTheme(stored, { persist: false });
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
this._applyFromPreference();
|
|
1316
|
+
}
|
|
1317
|
+
_readStorage() {
|
|
1318
|
+
const stored = localStorage.getItem("velin-theme");
|
|
1319
|
+
this._applyTheme(stored || "", { persist: false });
|
|
1320
|
+
}
|
|
1321
|
+
_applyFromPreference() {
|
|
1322
|
+
if (localStorage.getItem("velin-theme")) return;
|
|
1323
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1324
|
+
this._applyTheme(prefersDark ? "dark" : "", { persist: false });
|
|
1325
|
+
}
|
|
1326
|
+
_applyTheme(slug, { persist }) {
|
|
1327
|
+
const normalized = !slug || slug === "light" ? "" : slug;
|
|
1328
|
+
if (!this._target) return;
|
|
1329
|
+
if (!normalized) {
|
|
1330
|
+
this._target.removeAttribute("data-velin-theme");
|
|
1331
|
+
this.removeAttribute("theme");
|
|
1332
|
+
} else {
|
|
1333
|
+
this._target.setAttribute("data-velin-theme", normalized);
|
|
1334
|
+
this.setAttribute("theme", normalized === "dark" ? "dark" : normalized);
|
|
1335
|
+
ensureThemeStylesheet(normalized, this._themesBase);
|
|
1336
|
+
}
|
|
1337
|
+
if (persist) {
|
|
1338
|
+
if (!normalized) localStorage.removeItem("velin-theme");
|
|
1339
|
+
else localStorage.setItem("velin-theme", normalized);
|
|
1340
|
+
}
|
|
1341
|
+
this._highlightActive();
|
|
1342
|
+
this.dispatchEvent(new CustomEvent("velin-theme-change", {
|
|
1343
|
+
bubbles: true,
|
|
1344
|
+
detail: { theme: normalized || "light", dark: normalized === "dark", slug: normalized }
|
|
1345
|
+
}));
|
|
1346
|
+
}
|
|
1347
|
+
_toggleDarkMode() {
|
|
1348
|
+
const current = this._currentSlug();
|
|
1349
|
+
const next = current === "dark" ? "" : "dark";
|
|
1350
|
+
this._applyTheme(next, { persist: true });
|
|
1351
|
+
}
|
|
1092
1352
|
};
|
|
1093
1353
|
customElements.define("velin-theme-toggle", VelinThemeToggle);
|
|
1094
1354
|
var velin_theme_toggle_default = VelinThemeToggle;
|
|
@@ -2432,6 +2692,1306 @@ var VelinPersist = class extends HTMLElement {
|
|
|
2432
2692
|
customElements.define("velin-persist", VelinPersist);
|
|
2433
2693
|
var velin_persist_default = VelinPersist;
|
|
2434
2694
|
|
|
2695
|
+
// components/velin-combobox.js
|
|
2696
|
+
var styles19 = `
|
|
2697
|
+
:host { display: inline-block; position: relative; }
|
|
2698
|
+
.listbox {
|
|
2699
|
+
position: absolute; z-index: var(--velin-z-dropdown, 100);
|
|
2700
|
+
inset-block-start: 100%; inset-inline-start: 0;
|
|
2701
|
+
min-inline-size: 100%; margin-block-start: var(--velin-space-1, 0.25rem);
|
|
2702
|
+
padding-block: var(--velin-space-1, 0.25rem);
|
|
2703
|
+
background: var(--velin-color-surface-bright, #fff);
|
|
2704
|
+
border: 1px solid var(--velin-color-border, #ddd);
|
|
2705
|
+
border-radius: var(--velin-radius-md, 0.5rem);
|
|
2706
|
+
box-shadow: var(--velin-shadow-lg, 0 10px 15px rgba(0,0,0,0.08));
|
|
2707
|
+
opacity: 0; visibility: hidden;
|
|
2708
|
+
transition: opacity 150ms ease, visibility 150ms ease;
|
|
2709
|
+
}
|
|
2710
|
+
:host([open]) .listbox { opacity: 1; visibility: visible; }
|
|
2711
|
+
::slotted([role="option"]) {
|
|
2712
|
+
display: block; inline-size: 100%;
|
|
2713
|
+
padding: var(--velin-space-2, 0.5rem) var(--velin-space-4, 1rem);
|
|
2714
|
+
min-block-size: 2.5rem;
|
|
2715
|
+
text-align: start; background: none; border: none;
|
|
2716
|
+
cursor: pointer; font-size: var(--velin-text-base, 1rem);
|
|
2717
|
+
}
|
|
2718
|
+
::slotted([role="option"][aria-selected="true"]) {
|
|
2719
|
+
background: var(--velin-color-surface-dim, #eee);
|
|
2720
|
+
}
|
|
2721
|
+
`;
|
|
2722
|
+
var VelinCombobox = class extends HTMLElement {
|
|
2723
|
+
static get observedAttributes() {
|
|
2724
|
+
return ["open", "aria-label"];
|
|
2725
|
+
}
|
|
2726
|
+
constructor() {
|
|
2727
|
+
super();
|
|
2728
|
+
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
2729
|
+
this._onDocClick = this._onDocClick.bind(this);
|
|
2730
|
+
this._onKey = this._onKey.bind(this);
|
|
2731
|
+
}
|
|
2732
|
+
connectedCallback() {
|
|
2733
|
+
const listId = `velin-combobox-list-${Math.random().toString(36).slice(2, 9)}`;
|
|
2734
|
+
this._listId = listId;
|
|
2735
|
+
const listLabel = escapeHTML(this.getAttribute("aria-label") || "Options");
|
|
2736
|
+
this.shadowRoot.innerHTML = `
|
|
2737
|
+
<style>${styles19}</style>
|
|
2738
|
+
<slot name="trigger"></slot>
|
|
2739
|
+
<div class="listbox" id="${listId}" role="listbox" aria-label="${listLabel}" part="listbox"><slot></slot></div>
|
|
2740
|
+
`;
|
|
2741
|
+
const triggerSlot = this.shadowRoot.querySelector('slot[name="trigger"]');
|
|
2742
|
+
triggerSlot.addEventListener("slotchange", () => this._wireTrigger());
|
|
2743
|
+
this.shadowRoot.querySelector("slot:not([name])")?.addEventListener("slotchange", () => this._wireOptions());
|
|
2744
|
+
this._wireTrigger();
|
|
2745
|
+
this._wireOptions();
|
|
2746
|
+
this.addEventListener("keydown", this._onKey);
|
|
2747
|
+
}
|
|
2748
|
+
_wireTrigger() {
|
|
2749
|
+
const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
|
|
2750
|
+
if (!trigger) return;
|
|
2751
|
+
trigger.setAttribute("role", "combobox");
|
|
2752
|
+
trigger.setAttribute("aria-expanded", this.hasAttribute("open") ? "true" : "false");
|
|
2753
|
+
trigger.setAttribute("aria-controls", this._listId);
|
|
2754
|
+
trigger.setAttribute("aria-autocomplete", "list");
|
|
2755
|
+
if (!trigger.id) trigger.id = `velin-combobox-trigger-${Math.random().toString(36).slice(2, 9)}`;
|
|
2756
|
+
const list = this.shadowRoot.querySelector(".listbox");
|
|
2757
|
+
if (list) list.setAttribute("aria-labelledby", trigger.id);
|
|
2758
|
+
if (!trigger.dataset.velinComboWired) {
|
|
2759
|
+
trigger.dataset.velinComboWired = "1";
|
|
2760
|
+
trigger.addEventListener("click", () => this.toggle());
|
|
2761
|
+
trigger.addEventListener("keydown", (e) => {
|
|
2762
|
+
if (e.key === "ArrowDown" || e.key === "Enter") {
|
|
2763
|
+
e.preventDefault();
|
|
2764
|
+
this.open();
|
|
2765
|
+
}
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
_wireOptions() {
|
|
2770
|
+
const options = this._getOptions();
|
|
2771
|
+
options.forEach((el, i) => {
|
|
2772
|
+
el.setAttribute("role", "option");
|
|
2773
|
+
el.setAttribute("aria-selected", el.hasAttribute("selected") ? "true" : "false");
|
|
2774
|
+
el.setAttribute("tabindex", i === 0 ? "0" : "-1");
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
_getOptions() {
|
|
2778
|
+
const slot = this.shadowRoot.querySelector("slot:not([name])");
|
|
2779
|
+
return slot ? slot.assignedElements().filter((el) => !el.hidden) : [];
|
|
2780
|
+
}
|
|
2781
|
+
toggle() {
|
|
2782
|
+
this.hasAttribute("open") ? this.close() : this.open();
|
|
2783
|
+
}
|
|
2784
|
+
open() {
|
|
2785
|
+
this.setAttribute("open", "");
|
|
2786
|
+
const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
|
|
2787
|
+
if (trigger) trigger.setAttribute("aria-expanded", "true");
|
|
2788
|
+
document.addEventListener("click", this._onDocClick, true);
|
|
2789
|
+
requestAnimationFrame(() => {
|
|
2790
|
+
const opts = this._getOptions();
|
|
2791
|
+
if (opts.length) opts[0].focus();
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
close() {
|
|
2795
|
+
this.removeAttribute("open");
|
|
2796
|
+
const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
|
|
2797
|
+
if (trigger) {
|
|
2798
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
2799
|
+
trigger.focus();
|
|
2800
|
+
}
|
|
2801
|
+
document.removeEventListener("click", this._onDocClick, true);
|
|
2802
|
+
this.dispatchEvent(new CustomEvent("velin-close", { bubbles: true }));
|
|
2803
|
+
}
|
|
2804
|
+
_onDocClick(e) {
|
|
2805
|
+
if (!this.contains(e.target)) this.close();
|
|
2806
|
+
}
|
|
2807
|
+
_onKey(e) {
|
|
2808
|
+
if (!this.hasAttribute("open")) return;
|
|
2809
|
+
if (e.key === "Escape") {
|
|
2810
|
+
this.close();
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
const options = this._getOptions();
|
|
2814
|
+
if (!options.length) return;
|
|
2815
|
+
rovingTabindex(this, options, e);
|
|
2816
|
+
if (e.key === "Enter" && options.includes(e.target)) {
|
|
2817
|
+
this._selectOption(e.target);
|
|
2818
|
+
this.close();
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
_selectOption(el) {
|
|
2822
|
+
this._getOptions().forEach((o) => o.setAttribute("aria-selected", o === el ? "true" : "false"));
|
|
2823
|
+
const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
|
|
2824
|
+
if (trigger && "value" in trigger) trigger.value = el.textContent?.trim() || "";
|
|
2825
|
+
this.dispatchEvent(new CustomEvent("velin-select", { bubbles: true, detail: { option: el } }));
|
|
2826
|
+
}
|
|
2827
|
+
attributeChangedCallback(name) {
|
|
2828
|
+
if (name === "open") this._wireTrigger();
|
|
2829
|
+
if (name === "aria-label") {
|
|
2830
|
+
const list = this.shadowRoot?.querySelector(".listbox");
|
|
2831
|
+
if (list) list.setAttribute("aria-label", escapeHTML(this.getAttribute("aria-label") || "Options"));
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
disconnectedCallback() {
|
|
2835
|
+
document.removeEventListener("click", this._onDocClick, true);
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
customElements.define("velin-combobox", VelinCombobox);
|
|
2839
|
+
var velin_combobox_default = VelinCombobox;
|
|
2840
|
+
|
|
2841
|
+
// components/velin-bottom-nav.js
|
|
2842
|
+
var styles20 = `
|
|
2843
|
+
:host { display: block; }
|
|
2844
|
+
nav {
|
|
2845
|
+
display: flex;
|
|
2846
|
+
justify-content: space-around;
|
|
2847
|
+
align-items: center;
|
|
2848
|
+
padding: var(--velin-space-2, 0.5rem) var(--velin-space-4, 1rem);
|
|
2849
|
+
padding-block-end: max(var(--velin-space-2, 0.5rem), env(safe-area-inset-bottom, 0px));
|
|
2850
|
+
background: var(--velin-color-surface-bright, #fff);
|
|
2851
|
+
border-block-start: 1px solid var(--velin-color-border, #ddd);
|
|
2852
|
+
}
|
|
2853
|
+
::slotted(a), ::slotted(button) {
|
|
2854
|
+
display: flex;
|
|
2855
|
+
flex-direction: column;
|
|
2856
|
+
align-items: center;
|
|
2857
|
+
gap: var(--velin-space-1, 0.25rem);
|
|
2858
|
+
min-inline-size: 2.75rem;
|
|
2859
|
+
min-block-size: 2.75rem;
|
|
2860
|
+
padding: var(--velin-space-2, 0.5rem);
|
|
2861
|
+
font-size: var(--velin-text-xs, 0.75rem);
|
|
2862
|
+
color: var(--velin-color-text-muted, #666);
|
|
2863
|
+
text-decoration: none;
|
|
2864
|
+
background: none;
|
|
2865
|
+
border: none;
|
|
2866
|
+
cursor: pointer;
|
|
2867
|
+
}
|
|
2868
|
+
::slotted([current]) {
|
|
2869
|
+
color: var(--velin-color-primary, #2563eb);
|
|
2870
|
+
font-weight: var(--velin-weight-semibold, 600);
|
|
2871
|
+
}
|
|
2872
|
+
`;
|
|
2873
|
+
var VelinBottomNav = class extends HTMLElement {
|
|
2874
|
+
constructor() {
|
|
2875
|
+
super();
|
|
2876
|
+
this.attachShadow({ mode: "open" });
|
|
2877
|
+
this._onSlot = this._onSlot.bind(this);
|
|
2878
|
+
}
|
|
2879
|
+
connectedCallback() {
|
|
2880
|
+
const label = escapeHTML(this.getAttribute("aria-label") || "Bottom navigation");
|
|
2881
|
+
this.shadowRoot.innerHTML = `
|
|
2882
|
+
<style>${styles20}</style>
|
|
2883
|
+
<nav role="navigation" aria-label="${label}"><slot></slot></nav>
|
|
2884
|
+
`;
|
|
2885
|
+
const slot = this.shadowRoot.querySelector("slot");
|
|
2886
|
+
slot.addEventListener("slotchange", this._onSlot);
|
|
2887
|
+
this._onSlot();
|
|
2888
|
+
}
|
|
2889
|
+
_onSlot() {
|
|
2890
|
+
this._syncCurrent();
|
|
2891
|
+
}
|
|
2892
|
+
_syncCurrent() {
|
|
2893
|
+
const slot = this.shadowRoot?.querySelector("slot");
|
|
2894
|
+
if (!slot) return;
|
|
2895
|
+
const hostKey = this.getAttribute("current");
|
|
2896
|
+
slot.assignedElements().forEach((el) => {
|
|
2897
|
+
const active = el.hasAttribute("current") || hostKey && (el.dataset.nav === hostKey || el.getAttribute("data-nav") === hostKey);
|
|
2898
|
+
if (active) {
|
|
2899
|
+
el.setAttribute("current", "");
|
|
2900
|
+
el.setAttribute("aria-current", "page");
|
|
2901
|
+
} else {
|
|
2902
|
+
el.removeAttribute("current");
|
|
2903
|
+
el.removeAttribute("aria-current");
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
}
|
|
2907
|
+
static get observedAttributes() {
|
|
2908
|
+
return ["aria-label", "current"];
|
|
2909
|
+
}
|
|
2910
|
+
attributeChangedCallback(name) {
|
|
2911
|
+
if (name === "aria-label") {
|
|
2912
|
+
const nav = this.shadowRoot?.querySelector("nav");
|
|
2913
|
+
if (nav) nav.setAttribute("aria-label", escapeHTML(this.getAttribute("aria-label") || "Bottom navigation"));
|
|
2914
|
+
}
|
|
2915
|
+
if (name === "current") this._syncCurrent();
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
customElements.define("velin-bottom-nav", VelinBottomNav);
|
|
2919
|
+
var velin_bottom_nav_default = VelinBottomNav;
|
|
2920
|
+
|
|
2921
|
+
// components/shadow-a11y-styles.js
|
|
2922
|
+
var SHADOW_A11Y_STYLES = `
|
|
2923
|
+
:host { display: block; }
|
|
2924
|
+
button, [role="button"] {
|
|
2925
|
+
min-inline-size: 2.75rem;
|
|
2926
|
+
min-block-size: 2.75rem;
|
|
2927
|
+
cursor: pointer;
|
|
2928
|
+
}
|
|
2929
|
+
button:focus-visible, [role="button"]:focus-visible {
|
|
2930
|
+
outline: 3px solid var(--velin-color-focus, #2563eb);
|
|
2931
|
+
outline-offset: 2px;
|
|
2932
|
+
}
|
|
2933
|
+
@media (forced-colors: active) {
|
|
2934
|
+
button, [role="button"] {
|
|
2935
|
+
border: 1px solid ButtonText;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
`;
|
|
2939
|
+
|
|
2940
|
+
// components/velin-sheet.js
|
|
2941
|
+
var styles21 = `
|
|
2942
|
+
${SHADOW_A11Y_STYLES}
|
|
2943
|
+
:host { display: contents; }
|
|
2944
|
+
.overlay {
|
|
2945
|
+
position: fixed; inset: 0; z-index: var(--velin-z-overlay, 400);
|
|
2946
|
+
background: var(--velin-color-overlay, rgba(0,0,0,0.4));
|
|
2947
|
+
opacity: 0; visibility: hidden;
|
|
2948
|
+
transition: opacity 200ms ease, visibility 200ms ease;
|
|
2949
|
+
}
|
|
2950
|
+
:host([open]) .overlay { opacity: 1; visibility: visible; }
|
|
2951
|
+
.sheet {
|
|
2952
|
+
position: fixed; inset-inline: 0; inset-block-end: 0;
|
|
2953
|
+
z-index: var(--velin-z-modal, 500);
|
|
2954
|
+
max-block-size: min(85vh, 32rem);
|
|
2955
|
+
background: var(--velin-color-surface-bright, #fff);
|
|
2956
|
+
border-radius: var(--velin-radius-lg, 0.75rem) var(--velin-radius-lg, 0.75rem) 0 0;
|
|
2957
|
+
box-shadow: var(--velin-shadow-xl, 0 -4px 24px rgba(0,0,0,0.12));
|
|
2958
|
+
display: flex; flex-direction: column;
|
|
2959
|
+
transform: translateY(100%);
|
|
2960
|
+
transition: transform 250ms ease;
|
|
2961
|
+
padding-block-end: env(safe-area-inset-bottom, 0px);
|
|
2962
|
+
}
|
|
2963
|
+
:host([open]) .sheet { transform: translateY(0); }
|
|
2964
|
+
.header {
|
|
2965
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
2966
|
+
padding: var(--velin-space-4, 1rem) var(--velin-space-5, 1.25rem);
|
|
2967
|
+
border-bottom: 1px solid var(--velin-color-border, #ddd);
|
|
2968
|
+
}
|
|
2969
|
+
.title { font-size: var(--velin-text-lg, 1.25rem); font-weight: 600; margin: 0; }
|
|
2970
|
+
.body { flex: 1; overflow-y: auto; padding: var(--velin-space-5, 1.25rem); }
|
|
2971
|
+
@media (prefers-reduced-motion: reduce) { .overlay, .sheet { transition: none; } }
|
|
2972
|
+
`;
|
|
2973
|
+
var VelinSheet = class extends HTMLElement {
|
|
2974
|
+
static get observedAttributes() {
|
|
2975
|
+
return ["open"];
|
|
2976
|
+
}
|
|
2977
|
+
constructor() {
|
|
2978
|
+
super();
|
|
2979
|
+
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
2980
|
+
this._prev = null;
|
|
2981
|
+
this._onKey = this._onKey.bind(this);
|
|
2982
|
+
}
|
|
2983
|
+
connectedCallback() {
|
|
2984
|
+
const title = escapeHTML(this.getAttribute("title") || this.getAttribute("label") || "");
|
|
2985
|
+
const titleId = "velin-sheet-title";
|
|
2986
|
+
this.shadowRoot.innerHTML = `
|
|
2987
|
+
<style>${styles21}</style>
|
|
2988
|
+
<div class="overlay" part="overlay"></div>
|
|
2989
|
+
<div class="sheet" role="dialog" aria-modal="true" aria-labelledby="${titleId}" part="sheet">
|
|
2990
|
+
<div class="header" part="header">
|
|
2991
|
+
<h2 class="title" id="${titleId}">${title}</h2>
|
|
2992
|
+
<button class="close-btn" aria-label="Close" part="close">×</button>
|
|
2993
|
+
</div>
|
|
2994
|
+
<div class="body" part="body"><slot></slot></div>
|
|
2995
|
+
</div>
|
|
2996
|
+
`;
|
|
2997
|
+
this.shadowRoot.querySelector(".close-btn").addEventListener("click", () => this.close());
|
|
2998
|
+
this.shadowRoot.querySelector(".overlay").addEventListener("click", () => this.close());
|
|
2999
|
+
if (this.hasAttribute("open")) this._open();
|
|
3000
|
+
}
|
|
3001
|
+
attributeChangedCallback(name) {
|
|
3002
|
+
if (name === "open") this.hasAttribute("open") ? this._open() : this._close();
|
|
3003
|
+
}
|
|
3004
|
+
open() {
|
|
3005
|
+
this.setAttribute("open", "");
|
|
3006
|
+
}
|
|
3007
|
+
close() {
|
|
3008
|
+
this.removeAttribute("open");
|
|
3009
|
+
this.dispatchEvent(new CustomEvent("velin-close", { bubbles: true }));
|
|
3010
|
+
}
|
|
3011
|
+
_open() {
|
|
3012
|
+
this._prev = saveFocus();
|
|
3013
|
+
setBackgroundInert(this);
|
|
3014
|
+
document.addEventListener("keydown", this._onKey);
|
|
3015
|
+
document.body.style.overflow = "hidden";
|
|
3016
|
+
requestAnimationFrame(() => {
|
|
3017
|
+
const f = getFocusableElements(this.shadowRoot);
|
|
3018
|
+
if (f.length) f[0].focus();
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
_close() {
|
|
3022
|
+
document.removeEventListener("keydown", this._onKey);
|
|
3023
|
+
document.body.style.overflow = "";
|
|
3024
|
+
clearBackgroundInert();
|
|
3025
|
+
restoreFocus(this._prev);
|
|
3026
|
+
}
|
|
3027
|
+
_onKey(e) {
|
|
3028
|
+
if (e.key === "Escape") {
|
|
3029
|
+
this.close();
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
trapFocus(this.shadowRoot, e);
|
|
3033
|
+
}
|
|
3034
|
+
disconnectedCallback() {
|
|
3035
|
+
document.removeEventListener("keydown", this._onKey);
|
|
3036
|
+
document.body.style.overflow = "";
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
customElements.define("velin-sheet", VelinSheet);
|
|
3040
|
+
var velin_sheet_default = VelinSheet;
|
|
3041
|
+
|
|
3042
|
+
// components/velin-segmented-control.js
|
|
3043
|
+
var styles22 = `
|
|
3044
|
+
${SHADOW_A11Y_STYLES}
|
|
3045
|
+
:host { display: block; }
|
|
3046
|
+
.group {
|
|
3047
|
+
display: inline-flex;
|
|
3048
|
+
gap: var(--velin-space-1, 0.25rem);
|
|
3049
|
+
padding: var(--velin-space-1, 0.25rem);
|
|
3050
|
+
background: var(--velin-color-surface-dim, #eee);
|
|
3051
|
+
border-radius: var(--velin-radius-md, 0.5rem);
|
|
3052
|
+
}
|
|
3053
|
+
::slotted(button) {
|
|
3054
|
+
min-inline-size: 2.75rem;
|
|
3055
|
+
min-block-size: 2.75rem;
|
|
3056
|
+
padding: var(--velin-space-2, 0.5rem) var(--velin-space-4, 1rem);
|
|
3057
|
+
border: none;
|
|
3058
|
+
border-radius: var(--velin-radius-sm, 0.25rem);
|
|
3059
|
+
background: transparent;
|
|
3060
|
+
color: var(--velin-color-text-muted, #666);
|
|
3061
|
+
cursor: pointer;
|
|
3062
|
+
font-size: var(--velin-text-sm, 0.875rem);
|
|
3063
|
+
}
|
|
3064
|
+
::slotted(button[aria-pressed="true"]) {
|
|
3065
|
+
background: var(--velin-color-surface-bright, #fff);
|
|
3066
|
+
color: var(--velin-color-text, #111);
|
|
3067
|
+
font-weight: var(--velin-weight-semibold, 600);
|
|
3068
|
+
box-shadow: var(--velin-shadow-sm, 0 1px 2px rgba(0,0,0,0.06));
|
|
3069
|
+
}
|
|
3070
|
+
`;
|
|
3071
|
+
var VelinSegmentedControl = class extends HTMLElement {
|
|
3072
|
+
constructor() {
|
|
3073
|
+
super();
|
|
3074
|
+
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
3075
|
+
this._onClick = this._onClick.bind(this);
|
|
3076
|
+
this._onKey = this._onKey.bind(this);
|
|
3077
|
+
}
|
|
3078
|
+
connectedCallback() {
|
|
3079
|
+
const label = escapeHTML(this.getAttribute("aria-label") || "Segmented control");
|
|
3080
|
+
this.shadowRoot.innerHTML = `
|
|
3081
|
+
<style>${styles22}</style>
|
|
3082
|
+
<div class="group" role="group" aria-label="${label}"><slot></slot></div>
|
|
3083
|
+
`;
|
|
3084
|
+
this.addEventListener("click", this._onClick);
|
|
3085
|
+
this.addEventListener("keydown", this._onKey);
|
|
3086
|
+
this.shadowRoot.querySelector("slot")?.addEventListener("slotchange", () => this._init());
|
|
3087
|
+
this._init();
|
|
3088
|
+
}
|
|
3089
|
+
_getButtons() {
|
|
3090
|
+
const slot = this.shadowRoot.querySelector("slot");
|
|
3091
|
+
return slot ? slot.assignedElements().filter((el) => el.tagName === "BUTTON") : [];
|
|
3092
|
+
}
|
|
3093
|
+
_init() {
|
|
3094
|
+
const buttons = this._getButtons();
|
|
3095
|
+
const selected = buttons.find((b) => b.hasAttribute("selected")) || buttons[0];
|
|
3096
|
+
buttons.forEach((btn, i) => {
|
|
3097
|
+
btn.setAttribute("aria-pressed", btn === selected ? "true" : "false");
|
|
3098
|
+
btn.setAttribute("tabindex", btn === selected ? "0" : "-1");
|
|
3099
|
+
});
|
|
3100
|
+
}
|
|
3101
|
+
_onClick(e) {
|
|
3102
|
+
const btn = e.target.closest("button");
|
|
3103
|
+
if (!btn || !this.contains(btn)) return;
|
|
3104
|
+
this._select(btn);
|
|
3105
|
+
}
|
|
3106
|
+
_select(btn) {
|
|
3107
|
+
this._getButtons().forEach((b) => {
|
|
3108
|
+
b.setAttribute("aria-pressed", b === btn ? "true" : "false");
|
|
3109
|
+
b.setAttribute("tabindex", b === btn ? "0" : "-1");
|
|
3110
|
+
});
|
|
3111
|
+
this.dispatchEvent(new CustomEvent("velin-change", { bubbles: true, detail: { value: btn.value || btn.textContent?.trim() } }));
|
|
3112
|
+
}
|
|
3113
|
+
_onKey(e) {
|
|
3114
|
+
const buttons = this._getButtons();
|
|
3115
|
+
if (!buttons.includes(e.target)) return;
|
|
3116
|
+
rovingTabindex(this, buttons, e);
|
|
3117
|
+
if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) {
|
|
3118
|
+
const focused = buttons.find((b) => b.getAttribute("tabindex") === "0");
|
|
3119
|
+
if (focused) this._select(focused);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
static get observedAttributes() {
|
|
3123
|
+
return ["aria-label"];
|
|
3124
|
+
}
|
|
3125
|
+
attributeChangedCallback(name) {
|
|
3126
|
+
if (name === "aria-label") {
|
|
3127
|
+
const group = this.shadowRoot?.querySelector(".group");
|
|
3128
|
+
if (group) group.setAttribute("aria-label", escapeHTML(this.getAttribute("aria-label") || "Segmented control"));
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
disconnectedCallback() {
|
|
3132
|
+
this.removeEventListener("click", this._onClick);
|
|
3133
|
+
this.removeEventListener("keydown", this._onKey);
|
|
3134
|
+
}
|
|
3135
|
+
};
|
|
3136
|
+
customElements.define("velin-segmented-control", VelinSegmentedControl);
|
|
3137
|
+
var velin_segmented_control_default = VelinSegmentedControl;
|
|
3138
|
+
|
|
3139
|
+
// components/velin-rating.js
|
|
3140
|
+
var styles23 = `
|
|
3141
|
+
${SHADOW_A11Y_STYLES}
|
|
3142
|
+
:host { display: inline-block; }
|
|
3143
|
+
.stars { display: inline-flex; gap: var(--velin-space-1, 0.25rem); }
|
|
3144
|
+
button {
|
|
3145
|
+
background: none; border: none; padding: var(--velin-space-1, 0.25rem);
|
|
3146
|
+
font-size: 1.5rem; line-height: 1; cursor: pointer;
|
|
3147
|
+
color: var(--velin-color-border, #ccc);
|
|
3148
|
+
}
|
|
3149
|
+
button[aria-checked="true"] { color: var(--velin-color-warning, #f59e0b); }
|
|
3150
|
+
`;
|
|
3151
|
+
var MAX = 5;
|
|
3152
|
+
var VelinRating = class extends HTMLElement {
|
|
3153
|
+
static get observedAttributes() {
|
|
3154
|
+
return ["value"];
|
|
3155
|
+
}
|
|
3156
|
+
constructor() {
|
|
3157
|
+
super();
|
|
3158
|
+
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
3159
|
+
this._onClick = this._onClick.bind(this);
|
|
3160
|
+
this._onKey = this._onKey.bind(this);
|
|
3161
|
+
}
|
|
3162
|
+
connectedCallback() {
|
|
3163
|
+
this.shadowRoot.innerHTML = `<style>${styles23}</style><div class="stars" role="radiogroup"></div>`;
|
|
3164
|
+
this._render();
|
|
3165
|
+
this.shadowRoot.querySelector(".stars").addEventListener("click", this._onClick);
|
|
3166
|
+
this.shadowRoot.querySelector(".stars").addEventListener("keydown", this._onKey);
|
|
3167
|
+
}
|
|
3168
|
+
_value() {
|
|
3169
|
+
const v = parseInt(this.getAttribute("value") || "0", 10);
|
|
3170
|
+
return Math.min(MAX, Math.max(0, Number.isNaN(v) ? 0 : v));
|
|
3171
|
+
}
|
|
3172
|
+
_render() {
|
|
3173
|
+
const group = this.shadowRoot.querySelector(".stars");
|
|
3174
|
+
const val = this._value();
|
|
3175
|
+
const label = escapeHTML(this.getAttribute("aria-label") || "Rating");
|
|
3176
|
+
group.setAttribute("aria-label", label);
|
|
3177
|
+
group.innerHTML = "";
|
|
3178
|
+
for (let i = 1; i <= MAX; i++) {
|
|
3179
|
+
const btn = document.createElement("button");
|
|
3180
|
+
btn.type = "button";
|
|
3181
|
+
btn.setAttribute("role", "radio");
|
|
3182
|
+
btn.setAttribute("aria-checked", i <= val ? "true" : "false");
|
|
3183
|
+
btn.setAttribute("aria-label", escapeHTML(`${i} star${i > 1 ? "s" : ""}`));
|
|
3184
|
+
btn.setAttribute("tabindex", i === (val || 1) ? "0" : "-1");
|
|
3185
|
+
btn.dataset.value = String(i);
|
|
3186
|
+
btn.textContent = i <= val ? "\u2605" : "\u2606";
|
|
3187
|
+
group.appendChild(btn);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
_getButtons() {
|
|
3191
|
+
return [...this.shadowRoot.querySelectorAll('button[role="radio"]')];
|
|
3192
|
+
}
|
|
3193
|
+
_onClick(e) {
|
|
3194
|
+
const btn = e.target.closest("button");
|
|
3195
|
+
if (!btn) return;
|
|
3196
|
+
this._setValue(parseInt(btn.dataset.value, 10));
|
|
3197
|
+
}
|
|
3198
|
+
_onKey(e) {
|
|
3199
|
+
const buttons = this._getButtons();
|
|
3200
|
+
if (!buttons.includes(e.target)) return;
|
|
3201
|
+
rovingTabindex(this, buttons, e);
|
|
3202
|
+
if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) {
|
|
3203
|
+
const focused = buttons.find((b) => b.getAttribute("tabindex") === "0");
|
|
3204
|
+
if (focused) this._setValue(parseInt(focused.dataset.value, 10));
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
_setValue(n) {
|
|
3208
|
+
this.setAttribute("value", String(n));
|
|
3209
|
+
this.dispatchEvent(new CustomEvent("velin-change", { bubbles: true, detail: { value: n } }));
|
|
3210
|
+
}
|
|
3211
|
+
attributeChangedCallback(name) {
|
|
3212
|
+
if (name === "value" && this.shadowRoot?.querySelector(".stars")) this._render();
|
|
3213
|
+
}
|
|
3214
|
+
};
|
|
3215
|
+
customElements.define("velin-rating", VelinRating);
|
|
3216
|
+
var velin_rating_default = VelinRating;
|
|
3217
|
+
|
|
3218
|
+
// components/velin-menubar.js
|
|
3219
|
+
var styles24 = `
|
|
3220
|
+
${SHADOW_A11Y_STYLES}
|
|
3221
|
+
:host { display: block; }
|
|
3222
|
+
.menubar {
|
|
3223
|
+
display: flex;
|
|
3224
|
+
flex-wrap: wrap;
|
|
3225
|
+
gap: var(--velin-space-1, 0.25rem);
|
|
3226
|
+
padding: var(--velin-space-2, 0.5rem);
|
|
3227
|
+
background: var(--velin-color-surface-bright, #fff);
|
|
3228
|
+
border-bottom: 1px solid var(--velin-color-border, #ddd);
|
|
3229
|
+
}
|
|
3230
|
+
::slotted([role="menuitem"]) {
|
|
3231
|
+
min-inline-size: 2.75rem;
|
|
3232
|
+
min-block-size: 2.75rem;
|
|
3233
|
+
padding: var(--velin-space-2, 0.5rem) var(--velin-space-4, 1rem);
|
|
3234
|
+
background: none;
|
|
3235
|
+
border: none;
|
|
3236
|
+
border-radius: var(--velin-radius-sm, 0.25rem);
|
|
3237
|
+
cursor: pointer;
|
|
3238
|
+
font-size: var(--velin-text-base, 1rem);
|
|
3239
|
+
color: var(--velin-color-text, #111);
|
|
3240
|
+
}
|
|
3241
|
+
::slotted([role="menuitem"]:hover) {
|
|
3242
|
+
background: var(--velin-color-surface-dim, #eee);
|
|
3243
|
+
}
|
|
3244
|
+
`;
|
|
3245
|
+
var VelinMenubar = class extends HTMLElement {
|
|
3246
|
+
constructor() {
|
|
3247
|
+
super();
|
|
3248
|
+
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
3249
|
+
this._onKey = this._onKey.bind(this);
|
|
3250
|
+
}
|
|
3251
|
+
connectedCallback() {
|
|
3252
|
+
const label = escapeHTML(this.getAttribute("aria-label") || "Menu bar");
|
|
3253
|
+
this.shadowRoot.innerHTML = `
|
|
3254
|
+
<style>${styles24}</style>
|
|
3255
|
+
<div class="menubar" role="menubar" aria-label="${label}"><slot></slot></div>
|
|
3256
|
+
`;
|
|
3257
|
+
this.addEventListener("keydown", this._onKey);
|
|
3258
|
+
this.shadowRoot.querySelector("slot")?.addEventListener("slotchange", () => this._init());
|
|
3259
|
+
this._init();
|
|
3260
|
+
}
|
|
3261
|
+
_getItems() {
|
|
3262
|
+
const slot = this.shadowRoot.querySelector("slot");
|
|
3263
|
+
return slot ? slot.assignedElements().filter((el) => !el.hasAttribute("disabled")) : [];
|
|
3264
|
+
}
|
|
3265
|
+
_init() {
|
|
3266
|
+
const items = this._getItems();
|
|
3267
|
+
items.forEach((el, i) => {
|
|
3268
|
+
if (!el.hasAttribute("role")) el.setAttribute("role", "menuitem");
|
|
3269
|
+
el.setAttribute("tabindex", i === 0 ? "0" : "-1");
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
_onKey(e) {
|
|
3273
|
+
const items = this._getItems();
|
|
3274
|
+
if (items.includes(e.target)) rovingTabindex(this, items, e);
|
|
3275
|
+
}
|
|
3276
|
+
static get observedAttributes() {
|
|
3277
|
+
return ["aria-label"];
|
|
3278
|
+
}
|
|
3279
|
+
attributeChangedCallback(name) {
|
|
3280
|
+
if (name === "aria-label") {
|
|
3281
|
+
const bar = this.shadowRoot?.querySelector(".menubar");
|
|
3282
|
+
if (bar) bar.setAttribute("aria-label", escapeHTML(this.getAttribute("aria-label") || "Menu bar"));
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
disconnectedCallback() {
|
|
3286
|
+
this.removeEventListener("keydown", this._onKey);
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
customElements.define("velin-menubar", VelinMenubar);
|
|
3290
|
+
var velin_menubar_default = VelinMenubar;
|
|
3291
|
+
|
|
3292
|
+
// components/velin-command.js
|
|
3293
|
+
var styles25 = `
|
|
3294
|
+
${SHADOW_A11Y_STYLES}
|
|
3295
|
+
:host { display: contents; }
|
|
3296
|
+
.overlay {
|
|
3297
|
+
position: fixed; inset: 0; z-index: var(--velin-z-modal, 500);
|
|
3298
|
+
display: flex; align-items: flex-start; justify-content: center;
|
|
3299
|
+
padding: 10vh var(--velin-space-4, 1rem) var(--velin-space-4, 1rem);
|
|
3300
|
+
background: var(--velin-color-overlay, rgba(0,0,0,0.4));
|
|
3301
|
+
opacity: 0; visibility: hidden;
|
|
3302
|
+
transition: opacity 150ms ease, visibility 150ms ease;
|
|
3303
|
+
}
|
|
3304
|
+
:host([open]) .overlay { opacity: 1; visibility: visible; }
|
|
3305
|
+
.panel {
|
|
3306
|
+
inline-size: min(32rem, 100%);
|
|
3307
|
+
background: var(--velin-color-surface-bright, #fff);
|
|
3308
|
+
border-radius: var(--velin-radius-lg, 0.75rem);
|
|
3309
|
+
box-shadow: var(--velin-shadow-xl, 0 20px 25px rgba(0,0,0,0.1));
|
|
3310
|
+
overflow: hidden;
|
|
3311
|
+
}
|
|
3312
|
+
.search {
|
|
3313
|
+
inline-size: 100%; padding: var(--velin-space-4, 1rem);
|
|
3314
|
+
border: none; border-bottom: 1px solid var(--velin-color-border, #ddd);
|
|
3315
|
+
font-size: var(--velin-text-base, 1rem);
|
|
3316
|
+
background: transparent;
|
|
3317
|
+
color: var(--velin-color-text, #111);
|
|
3318
|
+
}
|
|
3319
|
+
.results { max-block-size: 20rem; overflow-y: auto; padding: var(--velin-space-2, 0.5rem); }
|
|
3320
|
+
::slotted(button) {
|
|
3321
|
+
display: flex; inline-size: 100%;
|
|
3322
|
+
padding: var(--velin-space-3, 0.75rem) var(--velin-space-4, 1rem);
|
|
3323
|
+
min-block-size: 2.5rem;
|
|
3324
|
+
border: none; background: none; text-align: start;
|
|
3325
|
+
cursor: pointer; font-size: var(--velin-text-base, 1rem);
|
|
3326
|
+
border-radius: var(--velin-radius-sm, 0.25rem);
|
|
3327
|
+
}
|
|
3328
|
+
::slotted(button[hidden]) { display: none; }
|
|
3329
|
+
::slotted(button:focus-visible) {
|
|
3330
|
+
background: var(--velin-color-surface-dim, #eee);
|
|
3331
|
+
}
|
|
3332
|
+
`;
|
|
3333
|
+
var VelinCommand = class extends HTMLElement {
|
|
3334
|
+
static get observedAttributes() {
|
|
3335
|
+
return ["open"];
|
|
3336
|
+
}
|
|
3337
|
+
constructor() {
|
|
3338
|
+
super();
|
|
3339
|
+
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
3340
|
+
this._prev = null;
|
|
3341
|
+
this._onKey = this._onKey.bind(this);
|
|
3342
|
+
this._onInput = this._onInput.bind(this);
|
|
3343
|
+
}
|
|
3344
|
+
connectedCallback() {
|
|
3345
|
+
const placeholder = escapeHTML(this.getAttribute("placeholder") || "Search commands\u2026");
|
|
3346
|
+
this.shadowRoot.innerHTML = `
|
|
3347
|
+
<style>${styles25}</style>
|
|
3348
|
+
<div class="overlay" part="overlay">
|
|
3349
|
+
<div class="panel" role="dialog" aria-modal="true" aria-label="Command palette" part="panel">
|
|
3350
|
+
<input class="search" type="search" autocomplete="off" placeholder="${placeholder}" aria-label="Search" part="search" />
|
|
3351
|
+
<div class="results" part="results"><slot></slot></div>
|
|
3352
|
+
</div>
|
|
3353
|
+
</div>
|
|
3354
|
+
`;
|
|
3355
|
+
this.shadowRoot.querySelector(".search").addEventListener("input", this._onInput);
|
|
3356
|
+
this.shadowRoot.querySelector("slot")?.addEventListener("slotchange", () => this._filter(""));
|
|
3357
|
+
this._filter("");
|
|
3358
|
+
}
|
|
3359
|
+
attributeChangedCallback(name) {
|
|
3360
|
+
if (name === "open") this.hasAttribute("open") ? this._open() : this._close();
|
|
3361
|
+
}
|
|
3362
|
+
open() {
|
|
3363
|
+
this.setAttribute("open", "");
|
|
3364
|
+
}
|
|
3365
|
+
close() {
|
|
3366
|
+
this.removeAttribute("open");
|
|
3367
|
+
this.dispatchEvent(new CustomEvent("velin-close", { bubbles: true }));
|
|
3368
|
+
}
|
|
3369
|
+
_open() {
|
|
3370
|
+
this._prev = saveFocus();
|
|
3371
|
+
setBackgroundInert(this);
|
|
3372
|
+
document.addEventListener("keydown", this._onKey);
|
|
3373
|
+
requestAnimationFrame(() => {
|
|
3374
|
+
this.shadowRoot.querySelector(".search")?.focus();
|
|
3375
|
+
this._filter("");
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
_close() {
|
|
3379
|
+
document.removeEventListener("keydown", this._onKey);
|
|
3380
|
+
clearBackgroundInert();
|
|
3381
|
+
restoreFocus(this._prev);
|
|
3382
|
+
const input = this.shadowRoot.querySelector(".search");
|
|
3383
|
+
if (input) input.value = "";
|
|
3384
|
+
this._filter("");
|
|
3385
|
+
}
|
|
3386
|
+
_onInput(e) {
|
|
3387
|
+
this._filter(e.target.value);
|
|
3388
|
+
}
|
|
3389
|
+
_filter(query) {
|
|
3390
|
+
const q = query.trim().toLowerCase();
|
|
3391
|
+
const slot = this.shadowRoot.querySelector("slot");
|
|
3392
|
+
slot?.assignedElements().forEach((btn) => {
|
|
3393
|
+
const text = btn.textContent?.trim().toLowerCase() || "";
|
|
3394
|
+
const match = !q || text.includes(q);
|
|
3395
|
+
btn.hidden = !match;
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
_onKey(e) {
|
|
3399
|
+
if (e.key === "Escape") {
|
|
3400
|
+
this.close();
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
trapFocus(this.shadowRoot, e);
|
|
3404
|
+
}
|
|
3405
|
+
disconnectedCallback() {
|
|
3406
|
+
document.removeEventListener("keydown", this._onKey);
|
|
3407
|
+
}
|
|
3408
|
+
};
|
|
3409
|
+
customElements.define("velin-command", VelinCommand);
|
|
3410
|
+
var velin_command_default = VelinCommand;
|
|
3411
|
+
|
|
3412
|
+
// components/velin-announcer.js
|
|
3413
|
+
var styles26 = `
|
|
3414
|
+
:host { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
|
3415
|
+
`;
|
|
3416
|
+
var VelinAnnouncer = class extends HTMLElement {
|
|
3417
|
+
connectedCallback() {
|
|
3418
|
+
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
3419
|
+
const live = this.getAttribute("polite") === "false" ? "assertive" : "polite";
|
|
3420
|
+
this.shadowRoot.innerHTML = "<style>" + styles26 + '</style><div role="status" aria-live="' + live + '" aria-atomic="true" part="region"></div>';
|
|
3421
|
+
this._region = this.shadowRoot.querySelector('[role="status"]');
|
|
3422
|
+
}
|
|
3423
|
+
announce(message, { assertive = false } = {}) {
|
|
3424
|
+
if (!this._region) this.connectedCallback();
|
|
3425
|
+
this._region.setAttribute("aria-live", assertive ? "assertive" : "polite");
|
|
3426
|
+
this._region.textContent = "";
|
|
3427
|
+
requestAnimationFrame(() => {
|
|
3428
|
+
this._region.textContent = typeof message === "string" ? message : "";
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
static announceGlobal(message, options) {
|
|
3432
|
+
let el = document.querySelector("velin-announcer");
|
|
3433
|
+
if (!el) {
|
|
3434
|
+
el = document.createElement("velin-announcer");
|
|
3435
|
+
document.body.appendChild(el);
|
|
3436
|
+
}
|
|
3437
|
+
el.announce(message, options);
|
|
3438
|
+
}
|
|
3439
|
+
};
|
|
3440
|
+
customElements.define("velin-announcer", VelinAnnouncer);
|
|
3441
|
+
var velin_announcer_default = VelinAnnouncer;
|
|
3442
|
+
|
|
3443
|
+
// components/velin-sparkline.js
|
|
3444
|
+
var NS = "http://www.w3.org/2000/svg";
|
|
3445
|
+
function parseValues(raw) {
|
|
3446
|
+
if (!raw) return [];
|
|
3447
|
+
const trimmed = String(raw).trim();
|
|
3448
|
+
if (trimmed.startsWith("[")) {
|
|
3449
|
+
try {
|
|
3450
|
+
const parsed = JSON.parse(trimmed);
|
|
3451
|
+
return Array.isArray(parsed) ? parsed.map(Number).filter((n) => Number.isFinite(n)) : [];
|
|
3452
|
+
} catch {
|
|
3453
|
+
return [];
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
return trimmed.split(/[\s,]+/).map((s) => Number.parseFloat(s)).filter((n) => Number.isFinite(n));
|
|
3457
|
+
}
|
|
3458
|
+
function buildPoints(values, w, h, min, max) {
|
|
3459
|
+
const n = values.length;
|
|
3460
|
+
if (n === 0) return [];
|
|
3461
|
+
if (n === 1) {
|
|
3462
|
+
return [[0, h / 2], [w, h / 2]];
|
|
3463
|
+
}
|
|
3464
|
+
const range = Math.max(max - min, 1e-6);
|
|
3465
|
+
const stepX = w / (n - 1);
|
|
3466
|
+
return values.map((v, i) => {
|
|
3467
|
+
const x = i * stepX;
|
|
3468
|
+
const y = h - (v - min) / range * h;
|
|
3469
|
+
return [x, y];
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
function pointsToPath(points) {
|
|
3473
|
+
if (!points.length) return "";
|
|
3474
|
+
return points.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`).join(" ");
|
|
3475
|
+
}
|
|
3476
|
+
function pointsToArea(points, h) {
|
|
3477
|
+
if (!points.length) return "";
|
|
3478
|
+
const line = pointsToPath(points);
|
|
3479
|
+
const last = points[points.length - 1][0];
|
|
3480
|
+
const first = points[0][0];
|
|
3481
|
+
return `${line} L${last.toFixed(2)},${h} L${first.toFixed(2)},${h} Z`;
|
|
3482
|
+
}
|
|
3483
|
+
var VelinSparkline = class extends HTMLElement {
|
|
3484
|
+
static get observedAttributes() {
|
|
3485
|
+
return ["values", "width", "height", "min", "max", "area", "glow", "animate", "label"];
|
|
3486
|
+
}
|
|
3487
|
+
constructor() {
|
|
3488
|
+
super();
|
|
3489
|
+
this._values = [];
|
|
3490
|
+
this._gradientId = `velin-spark-grad-${Math.random().toString(36).slice(2, 8)}`;
|
|
3491
|
+
}
|
|
3492
|
+
connectedCallback() {
|
|
3493
|
+
this._render();
|
|
3494
|
+
}
|
|
3495
|
+
attributeChangedCallback() {
|
|
3496
|
+
if (this.isConnected) this._render();
|
|
3497
|
+
}
|
|
3498
|
+
get values() {
|
|
3499
|
+
return this._values.slice();
|
|
3500
|
+
}
|
|
3501
|
+
set values(arr) {
|
|
3502
|
+
if (!Array.isArray(arr)) return;
|
|
3503
|
+
this._values = arr.filter((n) => Number.isFinite(Number(n))).map(Number);
|
|
3504
|
+
this.setAttribute("values", this._values.join(","));
|
|
3505
|
+
}
|
|
3506
|
+
update(values) {
|
|
3507
|
+
if (!Array.isArray(values)) return;
|
|
3508
|
+
this._values = values.filter((n) => Number.isFinite(Number(n))).map(Number);
|
|
3509
|
+
this._render({ tick: true });
|
|
3510
|
+
}
|
|
3511
|
+
_render({ tick = false } = {}) {
|
|
3512
|
+
const w = Number.parseFloat(this.getAttribute("width")) || 320;
|
|
3513
|
+
const h = Number.parseFloat(this.getAttribute("height")) || 96;
|
|
3514
|
+
const values = this._values.length ? this._values : parseValues(this.getAttribute("values"));
|
|
3515
|
+
this._values = values;
|
|
3516
|
+
if (!values.length) {
|
|
3517
|
+
this.innerHTML = "";
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
const minAttr = Number.parseFloat(this.getAttribute("min"));
|
|
3521
|
+
const maxAttr = Number.parseFloat(this.getAttribute("max"));
|
|
3522
|
+
const min = Number.isFinite(minAttr) ? minAttr : Math.min(...values);
|
|
3523
|
+
const max = Number.isFinite(maxAttr) ? maxAttr : Math.max(...values);
|
|
3524
|
+
const wantsArea = this.hasAttribute("area") && this.getAttribute("area") !== "false";
|
|
3525
|
+
const wantsGlow = this.hasAttribute("glow") && this.getAttribute("glow") !== "false";
|
|
3526
|
+
const animate = (this.getAttribute("animate") || "draw").toLowerCase();
|
|
3527
|
+
const label = this.getAttribute("label");
|
|
3528
|
+
const points = buildPoints(values, w, h, min, max);
|
|
3529
|
+
const linePath = pointsToPath(points);
|
|
3530
|
+
const areaPath = wantsArea ? pointsToArea(points, h) : "";
|
|
3531
|
+
this.innerHTML = "";
|
|
3532
|
+
const svg = document.createElementNS(NS, "svg");
|
|
3533
|
+
svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
3534
|
+
svg.setAttribute("preserveAspectRatio", "none");
|
|
3535
|
+
svg.style.display = "block";
|
|
3536
|
+
svg.style.width = "100%";
|
|
3537
|
+
svg.style.height = "100%";
|
|
3538
|
+
if (label) {
|
|
3539
|
+
svg.setAttribute("role", "img");
|
|
3540
|
+
svg.setAttribute("aria-label", label);
|
|
3541
|
+
} else {
|
|
3542
|
+
svg.setAttribute("aria-hidden", "true");
|
|
3543
|
+
}
|
|
3544
|
+
if (wantsArea) {
|
|
3545
|
+
const defs = document.createElementNS(NS, "defs");
|
|
3546
|
+
const grad = document.createElementNS(NS, "linearGradient");
|
|
3547
|
+
grad.setAttribute("id", this._gradientId);
|
|
3548
|
+
grad.setAttribute("x1", "0");
|
|
3549
|
+
grad.setAttribute("x2", "0");
|
|
3550
|
+
grad.setAttribute("y1", "0");
|
|
3551
|
+
grad.setAttribute("y2", "1");
|
|
3552
|
+
const stops = [
|
|
3553
|
+
["0%", "currentColor", "0.35"],
|
|
3554
|
+
["100%", "currentColor", "0"]
|
|
3555
|
+
];
|
|
3556
|
+
stops.forEach(([offset, color, op]) => {
|
|
3557
|
+
const stop = document.createElementNS(NS, "stop");
|
|
3558
|
+
stop.setAttribute("offset", offset);
|
|
3559
|
+
stop.setAttribute("stop-color", color);
|
|
3560
|
+
stop.setAttribute("stop-opacity", op);
|
|
3561
|
+
grad.appendChild(stop);
|
|
3562
|
+
});
|
|
3563
|
+
defs.appendChild(grad);
|
|
3564
|
+
svg.appendChild(defs);
|
|
3565
|
+
const area = document.createElementNS(NS, "path");
|
|
3566
|
+
area.setAttribute("d", areaPath);
|
|
3567
|
+
area.setAttribute("fill", `url(#${this._gradientId})`);
|
|
3568
|
+
area.setAttribute("stroke", "none");
|
|
3569
|
+
area.classList.add("velin-chart-area");
|
|
3570
|
+
svg.appendChild(area);
|
|
3571
|
+
}
|
|
3572
|
+
const line = document.createElementNS(NS, "path");
|
|
3573
|
+
line.setAttribute("d", linePath);
|
|
3574
|
+
line.setAttribute("fill", "none");
|
|
3575
|
+
line.setAttribute("stroke", "currentColor");
|
|
3576
|
+
line.setAttribute("stroke-width", "2");
|
|
3577
|
+
line.setAttribute("stroke-linecap", "round");
|
|
3578
|
+
line.setAttribute("stroke-linejoin", "round");
|
|
3579
|
+
line.setAttribute("vector-effect", "non-scaling-stroke");
|
|
3580
|
+
svg.appendChild(line);
|
|
3581
|
+
if (wantsGlow) svg.classList.add("velin-chart-glow");
|
|
3582
|
+
this.appendChild(svg);
|
|
3583
|
+
if (animate !== "none") {
|
|
3584
|
+
const len = typeof line.getTotalLength === "function" && line.getTotalLength() || w;
|
|
3585
|
+
line.style.setProperty("--velin-chart-len", len.toFixed(2));
|
|
3586
|
+
line.classList.add("velin-chart-line");
|
|
3587
|
+
} else {
|
|
3588
|
+
line.style.strokeDasharray = "";
|
|
3589
|
+
line.style.strokeDashoffset = "";
|
|
3590
|
+
}
|
|
3591
|
+
if (tick) {
|
|
3592
|
+
this.classList.remove("velin-spark-tick");
|
|
3593
|
+
void this.offsetWidth;
|
|
3594
|
+
this.classList.add("velin-spark-tick");
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
};
|
|
3598
|
+
if (typeof customElements !== "undefined" && !customElements.get("velin-sparkline")) {
|
|
3599
|
+
customElements.define("velin-sparkline", VelinSparkline);
|
|
3600
|
+
}
|
|
3601
|
+
var velin_sparkline_default = VelinSparkline;
|
|
3602
|
+
|
|
3603
|
+
// components/velin-counter.js
|
|
3604
|
+
var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
|
3605
|
+
function buildFormatter(host) {
|
|
3606
|
+
const format = (host.getAttribute("format") || "number").toLowerCase();
|
|
3607
|
+
const locale = host.getAttribute("locale") || void 0;
|
|
3608
|
+
const decimalsAttr = host.getAttribute("decimals");
|
|
3609
|
+
const decimals = decimalsAttr != null ? Math.max(0, Number.parseInt(decimalsAttr, 10) || 0) : null;
|
|
3610
|
+
const opts = {};
|
|
3611
|
+
if (decimals != null) {
|
|
3612
|
+
opts.minimumFractionDigits = decimals;
|
|
3613
|
+
opts.maximumFractionDigits = decimals;
|
|
3614
|
+
}
|
|
3615
|
+
if (format === "currency") {
|
|
3616
|
+
opts.style = "currency";
|
|
3617
|
+
opts.currency = host.getAttribute("currency") || "EUR";
|
|
3618
|
+
} else if (format === "percent") {
|
|
3619
|
+
opts.style = "percent";
|
|
3620
|
+
}
|
|
3621
|
+
try {
|
|
3622
|
+
return new Intl.NumberFormat(locale, opts);
|
|
3623
|
+
} catch {
|
|
3624
|
+
return new Intl.NumberFormat(void 0, opts);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
var VelinCounter = class extends HTMLElement {
|
|
3628
|
+
static get observedAttributes() {
|
|
3629
|
+
return ["from", "to", "duration", "decimals", "prefix", "suffix", "format", "currency", "locale"];
|
|
3630
|
+
}
|
|
3631
|
+
constructor() {
|
|
3632
|
+
super();
|
|
3633
|
+
this._rafId = 0;
|
|
3634
|
+
this._started = false;
|
|
3635
|
+
this._observer = null;
|
|
3636
|
+
}
|
|
3637
|
+
connectedCallback() {
|
|
3638
|
+
this._render(this._fromValue());
|
|
3639
|
+
if (this.getAttribute("autostart") === "false") return;
|
|
3640
|
+
this._scheduleStart();
|
|
3641
|
+
}
|
|
3642
|
+
disconnectedCallback() {
|
|
3643
|
+
cancelAnimationFrame(this._rafId);
|
|
3644
|
+
this._observer?.disconnect();
|
|
3645
|
+
}
|
|
3646
|
+
attributeChangedCallback(name) {
|
|
3647
|
+
if (!this.isConnected) return;
|
|
3648
|
+
if (name === "to" || name === "from") {
|
|
3649
|
+
this.start();
|
|
3650
|
+
} else {
|
|
3651
|
+
this._render(this._lastValue ?? this._toValue());
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
_fromValue() {
|
|
3655
|
+
return Number.parseFloat(this.getAttribute("from")) || 0;
|
|
3656
|
+
}
|
|
3657
|
+
_toValue() {
|
|
3658
|
+
return Number.parseFloat(this.getAttribute("to")) || 0;
|
|
3659
|
+
}
|
|
3660
|
+
_duration() {
|
|
3661
|
+
return Math.max(0, Number.parseFloat(this.getAttribute("duration")) || 900);
|
|
3662
|
+
}
|
|
3663
|
+
_scheduleStart() {
|
|
3664
|
+
if (this._started) return;
|
|
3665
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
3666
|
+
this.start();
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3669
|
+
this._observer = new IntersectionObserver((entries) => {
|
|
3670
|
+
for (const entry of entries) {
|
|
3671
|
+
if (entry.isIntersecting) {
|
|
3672
|
+
this.start();
|
|
3673
|
+
this._observer.disconnect();
|
|
3674
|
+
this._observer = null;
|
|
3675
|
+
break;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
}, { threshold: 0.2 });
|
|
3679
|
+
this._observer.observe(this);
|
|
3680
|
+
}
|
|
3681
|
+
start() {
|
|
3682
|
+
cancelAnimationFrame(this._rafId);
|
|
3683
|
+
this._started = true;
|
|
3684
|
+
const from = this._fromValue();
|
|
3685
|
+
const to = this._toValue();
|
|
3686
|
+
const duration = this._duration();
|
|
3687
|
+
const reduced = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3688
|
+
if (reduced || duration === 0) {
|
|
3689
|
+
this._render(to);
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3692
|
+
const start = performance.now();
|
|
3693
|
+
const tick = (now) => {
|
|
3694
|
+
const t = Math.min(1, (now - start) / duration);
|
|
3695
|
+
const value = from + (to - from) * easeOutExpo(t);
|
|
3696
|
+
this._render(value);
|
|
3697
|
+
if (t < 1) this._rafId = requestAnimationFrame(tick);
|
|
3698
|
+
};
|
|
3699
|
+
this._rafId = requestAnimationFrame(tick);
|
|
3700
|
+
}
|
|
3701
|
+
reset() {
|
|
3702
|
+
cancelAnimationFrame(this._rafId);
|
|
3703
|
+
this._started = false;
|
|
3704
|
+
this._render(this._fromValue());
|
|
3705
|
+
}
|
|
3706
|
+
_render(value) {
|
|
3707
|
+
this._lastValue = value;
|
|
3708
|
+
const formatter = buildFormatter(this);
|
|
3709
|
+
const prefix = this.getAttribute("prefix") || "";
|
|
3710
|
+
const suffix = this.getAttribute("suffix") || "";
|
|
3711
|
+
this.textContent = `${prefix}${formatter.format(value)}${suffix}`;
|
|
3712
|
+
}
|
|
3713
|
+
};
|
|
3714
|
+
if (typeof customElements !== "undefined" && !customElements.get("velin-counter")) {
|
|
3715
|
+
customElements.define("velin-counter", VelinCounter);
|
|
3716
|
+
}
|
|
3717
|
+
var velin_counter_default = VelinCounter;
|
|
3718
|
+
|
|
3719
|
+
// components/velin-live-dot.js
|
|
3720
|
+
var STATUS_COLORS = {
|
|
3721
|
+
live: "var(--velin-color-success, oklch(60% 0.16 145))",
|
|
3722
|
+
paused: "var(--velin-color-text-muted, oklch(60% 0.02 240))",
|
|
3723
|
+
warning: "var(--velin-color-warning, oklch(75% 0.16 80))",
|
|
3724
|
+
error: "var(--velin-color-danger, oklch(60% 0.2 25))",
|
|
3725
|
+
muted: "var(--velin-color-border, oklch(85% 0.01 240))"
|
|
3726
|
+
};
|
|
3727
|
+
var styles27 = `
|
|
3728
|
+
:host {
|
|
3729
|
+
display: inline-flex;
|
|
3730
|
+
align-items: center;
|
|
3731
|
+
gap: var(--velin-space-2, 0.5rem);
|
|
3732
|
+
font-size: inherit;
|
|
3733
|
+
color: inherit;
|
|
3734
|
+
line-height: 1.2;
|
|
3735
|
+
}
|
|
3736
|
+
.dot {
|
|
3737
|
+
inline-size: 0.55rem;
|
|
3738
|
+
block-size: 0.55rem;
|
|
3739
|
+
border-radius: 50%;
|
|
3740
|
+
background: var(--velin-live-color);
|
|
3741
|
+
flex-shrink: 0;
|
|
3742
|
+
}
|
|
3743
|
+
:host([pulse="false"]) .dot { animation: none; }
|
|
3744
|
+
:host(:not([pulse="false"])) .dot { animation: velin-live-pulse 1.8s var(--velin-ease-out, ease-out) infinite; }
|
|
3745
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3746
|
+
.dot { animation: none !important; }
|
|
3747
|
+
}
|
|
3748
|
+
`;
|
|
3749
|
+
var KEYFRAMES_FALLBACK = `
|
|
3750
|
+
@keyframes velin-live-pulse {
|
|
3751
|
+
0% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--velin-live-color) 65%, transparent); }
|
|
3752
|
+
70% { box-shadow: 0 0 0 0.6rem color-mix(in oklch, var(--velin-live-color) 0%, transparent); }
|
|
3753
|
+
100% { box-shadow: 0 0 0 0 transparent; }
|
|
3754
|
+
}`;
|
|
3755
|
+
var VelinLiveDot = class extends HTMLElement {
|
|
3756
|
+
static get observedAttributes() {
|
|
3757
|
+
return ["status", "pulse"];
|
|
3758
|
+
}
|
|
3759
|
+
constructor() {
|
|
3760
|
+
super();
|
|
3761
|
+
this.attachShadow({ mode: "open" });
|
|
3762
|
+
}
|
|
3763
|
+
connectedCallback() {
|
|
3764
|
+
this._render();
|
|
3765
|
+
}
|
|
3766
|
+
attributeChangedCallback() {
|
|
3767
|
+
if (this.shadowRoot) this._render();
|
|
3768
|
+
}
|
|
3769
|
+
_render() {
|
|
3770
|
+
const status = this.getAttribute("status") || "live";
|
|
3771
|
+
const color = STATUS_COLORS[status] || STATUS_COLORS.live;
|
|
3772
|
+
this.style.setProperty("--velin-live-color", color);
|
|
3773
|
+
this.shadowRoot.innerHTML = `
|
|
3774
|
+
<style>${styles27}${KEYFRAMES_FALLBACK}</style>
|
|
3775
|
+
<span class="dot" aria-hidden="true"></span><slot></slot>
|
|
3776
|
+
`;
|
|
3777
|
+
}
|
|
3778
|
+
};
|
|
3779
|
+
if (typeof customElements !== "undefined" && !customElements.get("velin-live-dot")) {
|
|
3780
|
+
customElements.define("velin-live-dot", VelinLiveDot);
|
|
3781
|
+
}
|
|
3782
|
+
var velin_live_dot_default = VelinLiveDot;
|
|
3783
|
+
|
|
3784
|
+
// components/velin-reveal.js
|
|
3785
|
+
var DEFAULTS = {
|
|
3786
|
+
selector: ".velin-animate-on-scroll",
|
|
3787
|
+
threshold: 0.1,
|
|
3788
|
+
rootMargin: "0px 0px -40px 0px",
|
|
3789
|
+
once: true,
|
|
3790
|
+
visibleClass: "is-visible"
|
|
3791
|
+
};
|
|
3792
|
+
var _activeObservers = /* @__PURE__ */ new WeakMap();
|
|
3793
|
+
function initReveal(options = {}) {
|
|
3794
|
+
if (typeof document === "undefined") return () => {
|
|
3795
|
+
};
|
|
3796
|
+
const opts = { ...DEFAULTS, ...options };
|
|
3797
|
+
const reduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3798
|
+
const targets = Array.from(document.querySelectorAll(opts.selector));
|
|
3799
|
+
if (reduced || typeof IntersectionObserver === "undefined") {
|
|
3800
|
+
targets.forEach((el) => el.classList.add(opts.visibleClass));
|
|
3801
|
+
return () => {
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
const observer = new IntersectionObserver(
|
|
3805
|
+
(entries) => {
|
|
3806
|
+
for (const entry of entries) {
|
|
3807
|
+
if (!entry.isIntersecting) continue;
|
|
3808
|
+
entry.target.classList.add(opts.visibleClass);
|
|
3809
|
+
if (opts.once) observer.unobserve(entry.target);
|
|
3810
|
+
}
|
|
3811
|
+
},
|
|
3812
|
+
{ threshold: opts.threshold, rootMargin: opts.rootMargin }
|
|
3813
|
+
);
|
|
3814
|
+
targets.forEach((el) => {
|
|
3815
|
+
if (_activeObservers.has(el)) return;
|
|
3816
|
+
_activeObservers.set(el, observer);
|
|
3817
|
+
observer.observe(el);
|
|
3818
|
+
});
|
|
3819
|
+
return () => {
|
|
3820
|
+
observer.disconnect();
|
|
3821
|
+
targets.forEach((el) => _activeObservers.delete(el));
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
if (typeof document !== "undefined") {
|
|
3825
|
+
const autoInit2 = () => {
|
|
3826
|
+
if (document.documentElement && document.documentElement.hasAttribute("data-velin-reveal-auto")) {
|
|
3827
|
+
initReveal();
|
|
3828
|
+
}
|
|
3829
|
+
};
|
|
3830
|
+
if (document.readyState === "loading") {
|
|
3831
|
+
document.addEventListener("DOMContentLoaded", autoInit2, { once: true });
|
|
3832
|
+
} else {
|
|
3833
|
+
autoInit2();
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
// components/velin-flip.js
|
|
3838
|
+
var REDUCED_MOTION_MQ = typeof window !== "undefined" && window.matchMedia ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
3839
|
+
var DEFAULTS2 = {
|
|
3840
|
+
duration: 250,
|
|
3841
|
+
easing: "var(--velin-ease-expo-out, cubic-bezier(0.16, 1, 0.3, 1))",
|
|
3842
|
+
itemSelector: ":scope > *"
|
|
3843
|
+
};
|
|
3844
|
+
function getItems(container, selector) {
|
|
3845
|
+
return Array.from(container.querySelectorAll(selector));
|
|
3846
|
+
}
|
|
3847
|
+
function flipReorder(container, mutateFn, options = {}) {
|
|
3848
|
+
if (!container || typeof mutateFn !== "function") return;
|
|
3849
|
+
const opts = { ...DEFAULTS2, ...options };
|
|
3850
|
+
const reduced = REDUCED_MOTION_MQ && REDUCED_MOTION_MQ.matches;
|
|
3851
|
+
const items = getItems(container, opts.itemSelector);
|
|
3852
|
+
const before = /* @__PURE__ */ new Map();
|
|
3853
|
+
items.forEach((el) => {
|
|
3854
|
+
if (!el.hidden) before.set(el, el.getBoundingClientRect());
|
|
3855
|
+
});
|
|
3856
|
+
mutateFn();
|
|
3857
|
+
if (reduced) return;
|
|
3858
|
+
const items2 = getItems(container, opts.itemSelector);
|
|
3859
|
+
items2.forEach((el) => {
|
|
3860
|
+
if (el.hidden) return;
|
|
3861
|
+
const prev = before.get(el);
|
|
3862
|
+
const next = el.getBoundingClientRect();
|
|
3863
|
+
if (!prev) {
|
|
3864
|
+
if (typeof el.animate !== "function") return;
|
|
3865
|
+
el.animate(
|
|
3866
|
+
[
|
|
3867
|
+
{ opacity: 0, transform: "scale(0.96)" },
|
|
3868
|
+
{ opacity: 1, transform: "scale(1)" }
|
|
3869
|
+
],
|
|
3870
|
+
{ duration: opts.duration, easing: opts.easing, fill: "both" }
|
|
3871
|
+
);
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
const dx = prev.left - next.left;
|
|
3875
|
+
const dy = prev.top - next.top;
|
|
3876
|
+
if (dx === 0 && dy === 0) return;
|
|
3877
|
+
if (typeof el.animate !== "function") return;
|
|
3878
|
+
el.animate(
|
|
3879
|
+
[
|
|
3880
|
+
{ transform: `translate(${dx}px, ${dy}px)` },
|
|
3881
|
+
{ transform: "translate(0, 0)" }
|
|
3882
|
+
],
|
|
3883
|
+
{ duration: opts.duration, easing: opts.easing, fill: "both" }
|
|
3884
|
+
);
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
function filterList(container, predicate, options = {}) {
|
|
3888
|
+
if (!container || typeof predicate !== "function") return;
|
|
3889
|
+
const opts = { ...DEFAULTS2, ...options };
|
|
3890
|
+
flipReorder(
|
|
3891
|
+
container,
|
|
3892
|
+
() => {
|
|
3893
|
+
getItems(container, opts.itemSelector).forEach((el) => {
|
|
3894
|
+
el.hidden = !predicate(el);
|
|
3895
|
+
});
|
|
3896
|
+
},
|
|
3897
|
+
opts
|
|
3898
|
+
);
|
|
3899
|
+
}
|
|
3900
|
+
function readTokens(value) {
|
|
3901
|
+
if (!value) return [];
|
|
3902
|
+
return String(value).toLowerCase().split(/[\s,|]+/).map((s) => s.trim()).filter(Boolean);
|
|
3903
|
+
}
|
|
3904
|
+
function matchTokens(itemTokens, queryTokens, mode) {
|
|
3905
|
+
if (!queryTokens.length) return true;
|
|
3906
|
+
if (mode === "all") return queryTokens.every((q) => itemTokens.includes(q));
|
|
3907
|
+
return queryTokens.some((q) => itemTokens.includes(q));
|
|
3908
|
+
}
|
|
3909
|
+
function matchSearch(item, query) {
|
|
3910
|
+
if (!query) return true;
|
|
3911
|
+
const haystack = (item.getAttribute("data-tags") || "") + " " + (item.getAttribute("data-search") || "") + " " + (item.textContent || "");
|
|
3912
|
+
return haystack.toLowerCase().includes(query.toLowerCase());
|
|
3913
|
+
}
|
|
3914
|
+
var FilterController = class {
|
|
3915
|
+
constructor(container) {
|
|
3916
|
+
this.container = container;
|
|
3917
|
+
this.tag = "";
|
|
3918
|
+
this.search = "";
|
|
3919
|
+
this.matchMode = container.getAttribute("data-velin-filter-mode") === "all" ? "all" : "any";
|
|
3920
|
+
this.itemSelector = container.getAttribute("data-velin-filter-item") || ":scope > *";
|
|
3921
|
+
}
|
|
3922
|
+
apply() {
|
|
3923
|
+
const queryTokens = readTokens(this.tag);
|
|
3924
|
+
const term = this.search;
|
|
3925
|
+
filterList(
|
|
3926
|
+
this.container,
|
|
3927
|
+
(el) => {
|
|
3928
|
+
const tokens = readTokens(el.getAttribute("data-tags"));
|
|
3929
|
+
return matchTokens(tokens, queryTokens, this.matchMode) && matchSearch(el, term);
|
|
3930
|
+
},
|
|
3931
|
+
{ itemSelector: this.itemSelector }
|
|
3932
|
+
);
|
|
3933
|
+
}
|
|
3934
|
+
};
|
|
3935
|
+
var _controllers = /* @__PURE__ */ new WeakMap();
|
|
3936
|
+
function getController(container) {
|
|
3937
|
+
let ctrl = _controllers.get(container);
|
|
3938
|
+
if (!ctrl) {
|
|
3939
|
+
ctrl = new FilterController(container);
|
|
3940
|
+
_controllers.set(container, ctrl);
|
|
3941
|
+
}
|
|
3942
|
+
return ctrl;
|
|
3943
|
+
}
|
|
3944
|
+
function resolveTarget(triggerEl) {
|
|
3945
|
+
const sel = triggerEl.getAttribute("data-velin-filter-target");
|
|
3946
|
+
if (!sel) return null;
|
|
3947
|
+
try {
|
|
3948
|
+
return document.querySelector(sel);
|
|
3949
|
+
} catch {
|
|
3950
|
+
return null;
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
function highlightActive(group, active) {
|
|
3954
|
+
if (!group) return;
|
|
3955
|
+
group.querySelectorAll("[data-velin-filter-value]").forEach((btn) => {
|
|
3956
|
+
if (btn === active) btn.setAttribute("data-velin-filter-active", "");
|
|
3957
|
+
else btn.removeAttribute("data-velin-filter-active");
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
function autoInit() {
|
|
3961
|
+
if (typeof document === "undefined") return;
|
|
3962
|
+
document.addEventListener("click", (event) => {
|
|
3963
|
+
const target = event.target.closest("[data-velin-filter-value]");
|
|
3964
|
+
if (!target) return;
|
|
3965
|
+
const container = resolveTarget(target);
|
|
3966
|
+
if (!container) return;
|
|
3967
|
+
const group = target.closest("[data-velin-filter-group]") || target.parentElement;
|
|
3968
|
+
highlightActive(group, target);
|
|
3969
|
+
const ctrl = getController(container);
|
|
3970
|
+
ctrl.tag = target.getAttribute("data-velin-filter-value") || "";
|
|
3971
|
+
if (ctrl.tag.toLowerCase() === "all" || ctrl.tag === "*") ctrl.tag = "";
|
|
3972
|
+
ctrl.apply();
|
|
3973
|
+
});
|
|
3974
|
+
const handleInput = (event) => {
|
|
3975
|
+
const input = event.target.closest("[data-velin-filter-input]");
|
|
3976
|
+
if (!input) return;
|
|
3977
|
+
const container = resolveTarget(input);
|
|
3978
|
+
if (!container) return;
|
|
3979
|
+
const ctrl = getController(container);
|
|
3980
|
+
const raw = input.value || (typeof input.getAttribute === "function" ? input.getAttribute("value") : "");
|
|
3981
|
+
ctrl.search = (raw || "").trim();
|
|
3982
|
+
ctrl.apply();
|
|
3983
|
+
};
|
|
3984
|
+
document.addEventListener("input", handleInput);
|
|
3985
|
+
document.addEventListener("change", handleInput);
|
|
3986
|
+
}
|
|
3987
|
+
if (typeof document !== "undefined") {
|
|
3988
|
+
if (document.readyState === "loading") {
|
|
3989
|
+
document.addEventListener("DOMContentLoaded", autoInit, { once: true });
|
|
3990
|
+
} else {
|
|
3991
|
+
autoInit();
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
|
|
2435
3995
|
// components/velin-haptic.js
|
|
2436
3996
|
var PATTERNS = {
|
|
2437
3997
|
tap: [10],
|
|
@@ -2489,22 +4049,33 @@ if (typeof document !== "undefined") {
|
|
|
2489
4049
|
export {
|
|
2490
4050
|
PATTERNS as HapticPatterns,
|
|
2491
4051
|
velin_accordion_default as VelinAccordion,
|
|
4052
|
+
velin_announcer_default as VelinAnnouncer,
|
|
4053
|
+
velin_bottom_nav_default as VelinBottomNav,
|
|
2492
4054
|
velin_carousel_default as VelinCarousel,
|
|
2493
4055
|
velin_collapse_default as VelinCollapse,
|
|
4056
|
+
velin_combobox_default as VelinCombobox,
|
|
4057
|
+
velin_command_default as VelinCommand,
|
|
2494
4058
|
velin_copy_default as VelinCopy,
|
|
2495
4059
|
velin_countdown_default as VelinCountdown,
|
|
4060
|
+
velin_counter_default as VelinCounter,
|
|
2496
4061
|
velin_dialog_default as VelinDialog,
|
|
2497
4062
|
velin_drawer_default as VelinDrawer,
|
|
2498
4063
|
velin_dropdown_default as VelinDropdown,
|
|
2499
4064
|
VelinHapticObserver,
|
|
2500
4065
|
velin_icon_default as VelinIcon,
|
|
2501
4066
|
velin_lightbox_default as VelinLightbox,
|
|
4067
|
+
velin_live_dot_default as VelinLiveDot,
|
|
4068
|
+
velin_menubar_default as VelinMenubar,
|
|
2502
4069
|
velin_modal_default as VelinModal,
|
|
2503
4070
|
velin_persist_default as VelinPersist,
|
|
2504
4071
|
velin_popover_default as VelinPopover,
|
|
2505
4072
|
velin_progress_ring_default as VelinProgressRing,
|
|
4073
|
+
velin_rating_default as VelinRating,
|
|
2506
4074
|
velin_scroll_top_default as VelinScrollTop,
|
|
2507
4075
|
velin_scrollspy_default as VelinScrollspy,
|
|
4076
|
+
velin_segmented_control_default as VelinSegmentedControl,
|
|
4077
|
+
velin_sheet_default as VelinSheet,
|
|
4078
|
+
velin_sparkline_default as VelinSparkline,
|
|
2508
4079
|
velin_stepper_wc_default as VelinStepperWC,
|
|
2509
4080
|
velin_tabs_default as VelinTabs,
|
|
2510
4081
|
velin_theme_toggle_default as VelinThemeToggle,
|
|
@@ -2512,11 +4083,20 @@ export {
|
|
|
2512
4083
|
velin_tooltip_wc_default as VelinTooltipWC,
|
|
2513
4084
|
applyHaptic,
|
|
2514
4085
|
clearBackgroundInert,
|
|
4086
|
+
createSafeHTML,
|
|
4087
|
+
escapeHTML,
|
|
4088
|
+
escapeHTMLAttribute,
|
|
4089
|
+
filterList,
|
|
4090
|
+
flipReorder,
|
|
2515
4091
|
getFocusableElements,
|
|
4092
|
+
getTrustedPolicy,
|
|
4093
|
+
initReveal,
|
|
2516
4094
|
restoreFocus,
|
|
2517
4095
|
rovingTabindex,
|
|
4096
|
+
sanitizeURL,
|
|
2518
4097
|
saveFocus,
|
|
2519
4098
|
setBackgroundInert,
|
|
4099
|
+
stripControlChars,
|
|
2520
4100
|
trapFocus,
|
|
2521
4101
|
vibrate
|
|
2522
4102
|
};
|