@vanduo-oss/framework 1.2.3 → 1.2.4

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 CHANGED
@@ -21,7 +21,7 @@
21
21
 
22
22
  A lightweight, pure HTML/CSS/JS framework for designing beautiful interfaces. Zero runtime dependencies, no mandatory build tools, just clean and simple code.
23
23
 
24
- [**Browse Full Documentation →**]([https://vanduo.dev/#docs](https://vanduo.dev/#docs)
24
+ [**Browse Full Documentation →**](https://vanduo.dev/#docs)
25
25
 
26
26
  ## Features
27
27
 
@@ -35,111 +35,76 @@ A lightweight, pure HTML/CSS/JS framework for designing beautiful interfaces. Ze
35
35
  - 🎛️ **Theme Customizer** - Real-time color, radius, font, and mode customization
36
36
  - 🔍 **SEO-Ready** - Comprehensive meta tags, structured data, and sitemap
37
37
 
38
- ### The Vanduo Way
39
- Stop wrapping everything in bloated container DOMs. Build beautiful, accessible UI components with clean, predictable utility classes:
40
-
41
- ```html
42
- <!-- Raw HTML -->
43
- <button>Click Me</button>
44
-
45
- <!-- With Vanduo Framework -->
46
- <button class="vd-btn vd-btn-primary vd-radius-full">
47
- <i class="ph ph-sparkle"></i> Click Me
48
- </button>
49
- ```
50
-
51
38
  ---
52
39
 
53
40
  ## Quick Start
54
41
 
55
- ### Option 1: Package Manager (Recommended)
42
+ ### Option 1: CDN (Recommended)
56
43
 
57
- **We strongly recommend using [pnpm](https://pnpm.io/)** for installing Vanduo Framework. Vanduo is strictly configured with `.npmrc` security policies (such as blocking exotic sub-dependencies and strict peer enforcement) that work best with inside the pnpm ecosystem.
58
-
59
- ```bash
60
- pnpm add @vanduo-oss/framework
61
- ```
62
-
63
- *(Note: While `npm install @vanduo-oss/framework` and `yarn add @vanduo-oss/framework` will still technically work, they do not inherently enforce the same strict lockfile and isolated `node_modules` security guarantees that pnpm provides out-of-the-box).*
64
-
65
- ### Option 2: CDN (Fastest)
66
-
67
- Load directly from jsDelivr — no download required:
44
+ The quickest way to get started no install, no build step. Add two lines to any HTML file:
68
45
 
69
46
  ```html
70
- <!DOCTYPE html>
71
- <html lang="en">
72
- <head>
73
- <meta charset="UTF-8">
74
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
75
- <title>My Website</title>
76
- <!-- Vanduo CSS via CDN -->
77
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@main/dist/vanduo.min.css">
78
- </head>
79
- <body>
80
- <!-- Your content here -->
81
-
82
- <!-- Vanduo JS via CDN -->
83
- <script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@main/dist/vanduo.min.js"></script>
84
- <script>Vanduo.init();</script>
85
- </body>
86
- </html>
47
+ <!-- Vanduo CSS -->
48
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@main/dist/vanduo.min.css">
49
+
50
+ <!-- Vanduo JS -->
51
+ <script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@main/dist/vanduo.min.js"></script>
52
+ <script>Vanduo.init();</script>
87
53
  ```
88
54
 
89
- **Pin to a specific version** (recommended for production):
55
+ **Pin to a specific version** for production:
90
56
  ```html
91
57
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.2.3/dist/vanduo.min.css">
92
58
  <script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.2.3/dist/vanduo.min.js"></script>
93
59
  <script>Vanduo.init();</script>
94
60
  ```
95
61
 
62
+ ### Option 2: Download
96
63
 
97
- ### Option 3: Download
98
-
99
- [**Download dist/ folder**](https://github.com/vanduo-oss/framework/tree/main/dist) and include locally:
64
+ [**Download the dist/ folder**](https://github.com/vanduo-oss/framework/tree/main/dist) and include locally — no internet connection required at runtime:
100
65
 
101
66
  ```html
102
- <!DOCTYPE html>
103
- <html lang="en">
104
- <head>
105
- <meta charset="UTF-8">
106
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
107
- <title>My Website</title>
108
- <link rel="stylesheet" href="dist/vanduo.min.css">
109
- </head>
110
- <body>
111
- <!-- Your content here -->
112
-
113
- <script src="dist/vanduo.min.js"></script>
114
- <script>Vanduo.init();</script>
115
- </body>
116
- </html>
67
+ <link rel="stylesheet" href="dist/vanduo.min.css">
68
+ <script src="dist/vanduo.min.js"></script>
69
+ <script>Vanduo.init();</script>
117
70
  ```
118
71
 
119
72
  The `dist/` folder is **self-contained** (CSS, JS, Fonts, Icons).
120
73
 
121
- ### Option 4: Source Files
74
+ ### Option 3: Source Files
122
75
 
123
76
  For development or when you need more control, use the unminified source:
124
77
 
125
78
  ```html
126
- <!DOCTYPE html>
127
- <html lang="en">
128
- <head>
129
- <meta charset="UTF-8">
130
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
131
- <title>My Website</title>
132
- <link rel="stylesheet" href="css/vanduo.css">
133
- </head>
134
- <body>
135
- <!-- Your content here -->
136
-
137
- <script src="js/vanduo.js"></script>
138
- <script>Vanduo.init();</script>
139
- </body>
140
- </html>
79
+ <link rel="stylesheet" href="css/vanduo.css">
80
+ <script src="js/vanduo.js"></script>
81
+ <script>Vanduo.init();</script>
82
+ ```
83
+
84
+ ### Option 4: With a Bundler (Vite)
85
+
86
+ > **Requires a build tool.** The imports below use bare module specifiers (`@vanduo-oss/framework`) which browsers cannot resolve on their own. For static HTML files, use the CDN or Download options above.
87
+
88
+ Scaffold a Vite project and install Vanduo:
89
+
90
+ ```bash
91
+ pnpm create vite my-app --template vanilla
92
+ cd my-app
93
+ pnpm add @vanduo-oss/framework
141
94
  ```
142
95
 
96
+ Import in your entry file (e.g. `main.js`):
97
+
98
+ ```js
99
+ import '@vanduo-oss/framework/css';
100
+ import { Vanduo } from '@vanduo-oss/framework';
101
+ Vanduo.init();
102
+ ```
103
+
104
+ **Why pnpm?** pnpm enforces a strict lockfile and creates an isolated `node_modules` structure. Vanduo's `.npmrc` security policies work best with pnpm out of the box.
105
+
106
+ *(Note: `npm install @vanduo-oss/framework` and `yarn add @vanduo-oss/framework` will also work, but they do not enforce the same strict lockfile and isolated `node_modules` security guarantees.)*
107
+
143
108
  ---
144
109
 
145
110
  ## LLM Access
@@ -166,7 +131,7 @@ Notes:
166
131
 
167
132
  Comprehensive documentation for all components, utilities, and customization options is available at vanduo.dev.
168
133
 
169
- [**View Documentation**]([https://vanduo.dev/#docs](https://vanduo.dev/#docs)
134
+ [**View Documentation**](https://vanduo.dev/#docs)
170
135
 
171
136
  ### Key Capabilities
172
137
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.2.3",
3
- "builtAt": "2026-03-02T18:16:51.602Z",
4
- "commit": "060fc43",
5
- "mode": "production"
2
+ "version": "1.2.4",
3
+ "builtAt": "2026-03-04T19:15:26.258Z",
4
+ "commit": "a524dfc",
5
+ "mode": "development+production"
6
6
  }
@@ -1,4 +1,4 @@
1
- /*! Vanduo v1.2.0 | Built: 2026-02-22T21:30:31.940Z | git:64c88fd | development */
1
+ /*! Vanduo v1.2.4 | Built: 2026-03-04T19:15:26.258Z | git:a524dfc | development */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -132,8 +132,9 @@ module.exports = __toCommonJS(index_exports);
132
132
  // js/vanduo.js
133
133
  (function() {
134
134
  "use strict";
135
+ const VANDUO_VERSION = true ? "1.2.4" : "0.0.0-dev";
135
136
  const Vanduo2 = {
136
- version: "1.2.0",
137
+ version: VANDUO_VERSION,
137
138
  components: {},
138
139
  /**
139
140
  * Initialize framework
@@ -168,7 +169,7 @@ module.exports = __toCommonJS(index_exports);
168
169
  }
169
170
  }
170
171
  });
171
- console.log("Vanduo Framework v1.2.0 initialized");
172
+ console.log("Vanduo Framework v" + this.version + " initialized");
172
173
  },
173
174
  /**
174
175
  * Register a component
@@ -5688,12 +5689,12 @@ module.exports = __toCommonJS(index_exports);
5688
5689
  const touchMoveHandler = (e) => {
5689
5690
  this.handleTouchMove(e, element);
5690
5691
  };
5691
- element.addEventListener("touchmove", touchMoveHandler);
5692
+ element.addEventListener("touchmove", touchMoveHandler, { passive: false });
5692
5693
  cleanupFunctions.push(() => element.removeEventListener("touchmove", touchMoveHandler));
5693
5694
  const touchEndHandler = (e) => {
5694
5695
  this.handleTouchEnd(e, element);
5695
5696
  };
5696
- element.addEventListener("touchend", touchEndHandler);
5697
+ element.addEventListener("touchend", touchEndHandler, { passive: false });
5697
5698
  cleanupFunctions.push(() => element.removeEventListener("touchend", touchEndHandler));
5698
5699
  const touchCancelHandler = (e) => {
5699
5700
  this.handleTouchEnd(e, element);
@@ -5899,7 +5900,7 @@ module.exports = __toCommonJS(index_exports);
5899
5900
  const deltaX = touch.clientX - this.touchState.startX;
5900
5901
  const deltaY = touch.clientY - this.touchState.startY;
5901
5902
  if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
5902
- e.preventDefault();
5903
+ if (e.cancelable) e.preventDefault();
5903
5904
  if (!this.touchState.isDragging) {
5904
5905
  this.touchState.isDragging = true;
5905
5906
  element.classList.add("is-dragging");
@@ -5944,7 +5945,7 @@ module.exports = __toCommonJS(index_exports);
5944
5945
  */
5945
5946
  handleTouchEnd: function(e, element) {
5946
5947
  if (this.touchState && this.touchState.isDragging) {
5947
- e.preventDefault();
5948
+ if (e.cancelable) e.preventDefault();
5948
5949
  element.classList.remove("is-dragging");
5949
5950
  element.classList.add("is-dropped");
5950
5951
  element.setAttribute("aria-grabbed", "false");
@@ -6172,6 +6173,237 @@ module.exports = __toCommonJS(index_exports);
6172
6173
  window.VanduoDraggable = Draggable;
6173
6174
  })();
6174
6175
 
6176
+ // js/components/lazy-load.js
6177
+ (function() {
6178
+ "use strict";
6179
+ const _observerMap = /* @__PURE__ */ new Map();
6180
+ function _isSafeUrl(url) {
6181
+ try {
6182
+ const resolved = new URL(url, window.location.href);
6183
+ return resolved.origin === window.location.origin;
6184
+ } catch (_) {
6185
+ return false;
6186
+ }
6187
+ }
6188
+ function _safeInjectHtml(containerEl, html) {
6189
+ const parser = new DOMParser();
6190
+ const doc = parser.parseFromString(html.trim(), "text/html");
6191
+ const DANGEROUS_TAGS = ["SCRIPT", "IFRAME", "OBJECT", "EMBED", "FORM", "BASE", "LINK", "META", "STYLE"];
6192
+ for (const tag of DANGEROUS_TAGS) {
6193
+ const els = doc.querySelectorAll(tag);
6194
+ for (let i = els.length - 1; i >= 0; i--) {
6195
+ els[i].parentNode.removeChild(els[i]);
6196
+ }
6197
+ }
6198
+ function _sanitizeNode(node) {
6199
+ if (node.nodeType === Node.ELEMENT_NODE) {
6200
+ const attrs = node.attributes;
6201
+ for (let i = attrs.length - 1; i >= 0; i--) {
6202
+ const attrName = attrs[i].name.toLowerCase();
6203
+ const attrValue = attrs[i].value.toLowerCase();
6204
+ const trimmedValue = attrValue.trim();
6205
+ if (attrName.startsWith("on") || trimmedValue.startsWith("javascript:") || trimmedValue.startsWith("data:") || trimmedValue.startsWith("vbscript:")) {
6206
+ node.removeAttribute(attrs[i].name);
6207
+ }
6208
+ }
6209
+ const children = node.childNodes;
6210
+ for (let i = 0; i < children.length; i++) {
6211
+ _sanitizeNode(children[i]);
6212
+ }
6213
+ }
6214
+ }
6215
+ _sanitizeNode(doc.body);
6216
+ const nodes = Array.from(doc.body.childNodes);
6217
+ while (containerEl.firstChild) {
6218
+ containerEl.removeChild(containerEl.firstChild);
6219
+ }
6220
+ nodes.forEach(function(node) {
6221
+ containerEl.appendChild(document.adoptNode(node));
6222
+ });
6223
+ }
6224
+ function _skeletonHtml() {
6225
+ return '<div class="vd-skeleton-card" style="position:relative;min-height:200px;padding:2rem;overflow:hidden;"><div class="vd-skeleton vd-skeleton-heading-lg" style="margin-bottom:1.5rem;"></div><div class="vd-skeleton vd-skeleton-paragraph"><div class="vd-skeleton vd-skeleton-text"></div><div class="vd-skeleton vd-skeleton-text"></div><div class="vd-skeleton vd-skeleton-text"></div></div><div class="vd-dynamic-loader" style="position:absolute;inset:0;"><div class="vd-dynamic-loader-grid"><div class="vd-spinner vd-spinner-sm vd-spinner-success" style="animation-delay:0s;"></div><div class="vd-spinner vd-spinner-sm vd-spinner-warning" style="animation-delay:-0.15s;"></div><div class="vd-spinner vd-spinner-sm vd-spinner-error" style="animation-delay:-0.3s;"></div><div class="vd-spinner vd-spinner-sm vd-spinner-info" style="animation-delay:-0.45s;"></div></div><span class="vd-dynamic-loader-text">Loading\u2026</span></div></div>';
6226
+ }
6227
+ function _spinnerHtml() {
6228
+ return '<div class="vd-dynamic-loader" style="min-height:180px;display:flex;align-items:center;justify-content:center;"><div class="vd-dynamic-loader-grid"><div class="vd-spinner vd-spinner-sm vd-spinner-success" style="animation-delay:0s;"></div><div class="vd-spinner vd-spinner-sm vd-spinner-warning" style="animation-delay:-0.15s;"></div><div class="vd-spinner vd-spinner-sm vd-spinner-error" style="animation-delay:-0.3s;"></div><div class="vd-spinner vd-spinner-sm vd-spinner-info" style="animation-delay:-0.45s;"></div></div><span class="vd-dynamic-loader-text">Loading\u2026</span></div>';
6229
+ }
6230
+ function _resolvePlaceholder(placeholder) {
6231
+ if (!placeholder || placeholder === "skeleton") return _skeletonHtml();
6232
+ if (placeholder === "spinner") return _spinnerHtml();
6233
+ return placeholder;
6234
+ }
6235
+ function _dispatch(el, eventName, detail) {
6236
+ el.dispatchEvent(new CustomEvent(eventName, { bubbles: true, detail: detail || {} }));
6237
+ }
6238
+ const VanduoLazyLoad = {
6239
+ /* ─────────────────────────────────────────────────
6240
+ * LOW-LEVEL API
6241
+ * ───────────────────────────────────────────────── */
6242
+ /**
6243
+ * Observe an element. `callback` is invoked once when the element
6244
+ * enters the viewport, then the element is automatically unobserved.
6245
+ *
6246
+ * @param {Element} element
6247
+ * @param {function(Element): void} callback
6248
+ * @param {{ threshold?: number, rootMargin?: string }} [options]
6249
+ */
6250
+ observe: function(element, callback, options) {
6251
+ if (!(element instanceof Element)) {
6252
+ console.warn("[VanduoLazyLoad] observe() requires a DOM Element.");
6253
+ return;
6254
+ }
6255
+ if (typeof callback !== "function") {
6256
+ console.warn("[VanduoLazyLoad] observe() requires a callback function.");
6257
+ return;
6258
+ }
6259
+ if (_observerMap.has(element)) return;
6260
+ const threshold = options && options.threshold != null ? options.threshold : 0;
6261
+ const rootMargin = options && options.rootMargin ? options.rootMargin : "0px";
6262
+ const observer = new IntersectionObserver(function(entries, obs) {
6263
+ entries.forEach(function(entry) {
6264
+ if (entry.isIntersecting) {
6265
+ obs.unobserve(entry.target);
6266
+ _observerMap.delete(entry.target);
6267
+ try {
6268
+ callback(entry.target);
6269
+ } catch (e) {
6270
+ console.error("[VanduoLazyLoad] Callback threw:", e);
6271
+ }
6272
+ }
6273
+ });
6274
+ }, { threshold, rootMargin });
6275
+ _observerMap.set(element, observer);
6276
+ observer.observe(element);
6277
+ },
6278
+ /**
6279
+ * Stop observing an element that was previously passed to observe().
6280
+ * @param {Element} element
6281
+ */
6282
+ unobserve: function(element) {
6283
+ const observer = _observerMap.get(element);
6284
+ if (observer) {
6285
+ observer.unobserve(element);
6286
+ _observerMap.delete(element);
6287
+ }
6288
+ },
6289
+ /**
6290
+ * Stop observing ALL currently observed elements.
6291
+ */
6292
+ unobserveAll: function() {
6293
+ _observerMap.forEach(function(observer, element) {
6294
+ observer.unobserve(element);
6295
+ });
6296
+ _observerMap.clear();
6297
+ },
6298
+ /* ─────────────────────────────────────────────────
6299
+ * HIGH-LEVEL API
6300
+ * ───────────────────────────────────────────────── */
6301
+ /**
6302
+ * Fetch an HTML partial and inject it into `containerEl` when the
6303
+ * container enters the viewport. A placeholder is shown immediately.
6304
+ *
6305
+ * @param {string} url URL of the HTML partial to fetch
6306
+ * @param {Element} containerEl Target element whose content will be replaced
6307
+ * @param {{
6308
+ * placeholder?: 'skeleton'|'spinner'|string,
6309
+ * threshold?: number,
6310
+ * rootMargin?: string,
6311
+ * onLoaded?: function(Element): void,
6312
+ * onError?: function(Error): void
6313
+ * }} [options]
6314
+ */
6315
+ loadSection: function(url, containerEl, options) {
6316
+ if (typeof url !== "string" || !url) {
6317
+ console.warn("[VanduoLazyLoad] loadSection() requires a non-empty URL string.");
6318
+ return;
6319
+ }
6320
+ if (!(containerEl instanceof Element)) {
6321
+ console.warn("[VanduoLazyLoad] loadSection() requires a DOM Element as containerEl.");
6322
+ return;
6323
+ }
6324
+ if (!_isSafeUrl(url)) {
6325
+ console.error("[VanduoLazyLoad] loadSection() blocked cross-origin URL:", url);
6326
+ return;
6327
+ }
6328
+ const opts = options || {};
6329
+ const placeholderHtml = _resolvePlaceholder(opts.placeholder);
6330
+ _safeInjectHtml(containerEl, placeholderHtml);
6331
+ _dispatch(containerEl, "lazysection:loading", { url });
6332
+ this.observe(containerEl, function() {
6333
+ const controller = new window.AbortController();
6334
+ const timeoutId = setTimeout(function() {
6335
+ controller.abort();
6336
+ }, 1e4);
6337
+ window.fetch(url, { signal: controller.signal }).then(function(res) {
6338
+ clearTimeout(timeoutId);
6339
+ if (!res.ok) throw new Error("HTTP " + res.status);
6340
+ return res.text();
6341
+ }).then(function(html) {
6342
+ _safeInjectHtml(containerEl, html);
6343
+ _dispatch(containerEl, "lazysection:loaded", { url });
6344
+ if (typeof window.Vanduo !== "undefined") {
6345
+ window.Vanduo.init();
6346
+ }
6347
+ if (typeof opts.onLoaded === "function") {
6348
+ opts.onLoaded(containerEl);
6349
+ }
6350
+ }).catch(function(err) {
6351
+ const alertEl = document.createElement("div");
6352
+ alertEl.className = "vd-alert vd-alert-error";
6353
+ alertEl.setAttribute("role", "alert");
6354
+ const msgEl = document.createElement("span");
6355
+ msgEl.textContent = "Failed to load content. ";
6356
+ const detailEl = document.createElement("small");
6357
+ detailEl.style.opacity = "0.7";
6358
+ detailEl.textContent = err.message;
6359
+ alertEl.appendChild(msgEl);
6360
+ alertEl.appendChild(detailEl);
6361
+ while (containerEl.firstChild) {
6362
+ containerEl.removeChild(containerEl.firstChild);
6363
+ }
6364
+ containerEl.appendChild(alertEl);
6365
+ _dispatch(containerEl, "lazysection:error", { url, error: err });
6366
+ console.error("[VanduoLazyLoad] loadSection failed:", err);
6367
+ if (typeof opts.onError === "function") {
6368
+ opts.onError(err);
6369
+ }
6370
+ });
6371
+ }, { threshold: opts.threshold, rootMargin: opts.rootMargin });
6372
+ },
6373
+ /* ─────────────────────────────────────────────────
6374
+ * ATTRIBUTE-DRIVEN INIT
6375
+ * ───────────────────────────────────────────────── */
6376
+ /**
6377
+ * Scan the DOM for [data-vd-lazy] elements and wire them up.
6378
+ * Safe to call multiple times — already-observed elements are skipped.
6379
+ */
6380
+ init: function() {
6381
+ const self = this;
6382
+ const elements = document.querySelectorAll("[data-vd-lazy]");
6383
+ elements.forEach(function(el) {
6384
+ if (_observerMap.has(el) || el.dataset.vdLazyState === "loading" || el.dataset.vdLazyState === "loaded") return;
6385
+ const url = el.getAttribute("data-vd-lazy");
6386
+ if (!url) return;
6387
+ el.dataset.vdLazyState = "loading";
6388
+ const placeholder = el.getAttribute("data-vd-lazy-placeholder") || "skeleton";
6389
+ self.loadSection(url, el, {
6390
+ placeholder,
6391
+ onLoaded: function() {
6392
+ el.dataset.vdLazyState = "loaded";
6393
+ },
6394
+ onError: function() {
6395
+ el.dataset.vdLazyState = "error";
6396
+ }
6397
+ });
6398
+ });
6399
+ }
6400
+ };
6401
+ if (typeof window.Vanduo !== "undefined") {
6402
+ window.Vanduo.register("LazyLoad", VanduoLazyLoad);
6403
+ }
6404
+ window.VanduoLazyLoad = VanduoLazyLoad;
6405
+ })();
6406
+
6175
6407
  // js/index.js
6176
6408
  var Vanduo = window.Vanduo;
6177
6409
  var index_default = Vanduo;