@mccustomapps/helaui 0.1.10 → 0.1.12

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
@@ -216,7 +216,7 @@ import '@mccustomapps/helaui/rain-background.css';
216
216
  const rain = new RainBackground();
217
217
  ```
218
218
 
219
- **Next.js (App Router)** — use a Client Component. `RainBackground` already mounts itself on `document.body`; do not insert `rain.element` a second time.
219
+ **Next.js (App Router)** — use a Client Component. `RainBackground` already mounts itself on `document.body`; do **not** call `insertBefore` / `appendChild` on `rain.element` a second time.
220
220
 
221
221
  ```tsx
222
222
  'use client';
@@ -227,7 +227,7 @@ import '@mccustomapps/helaui/rain-background.css';
227
227
 
228
228
  export function HelaRain() {
229
229
  useEffect(() => {
230
- const rain = new RainBackground();
230
+ const rain = new RainBackground({ zIndex: 0 });
231
231
  return () => rain.destroy();
232
232
  }, []);
233
233
 
@@ -241,7 +241,7 @@ export function HelaRain() {
241
241
  |---|---|---|---|
242
242
  | `images` | `string[]` | built-in artwork | Image URLs |
243
243
  | `density` | `number` | `28` | Number of particles |
244
- | `minSize` / `maxSize` | `number` | `28` | Particle width in pixels |
244
+ | `minSize` / `maxSize` | `number` | `28` / `72` | Particle width in pixels |
245
245
  | `minDuration` / `maxDuration` | `number` | `8` / `18` | Fall duration in seconds |
246
246
  | `target` | `HTMLElement` | `document.body` | Mount container |
247
247
  | `zIndex` | `number` | `0` | Stacking order |
package/dist/index.cjs CHANGED
@@ -405,16 +405,43 @@ function pick(items) {
405
405
  return items[Math.floor(Math.random() * items.length)];
406
406
  }
407
407
  function toImageUrl(source) {
408
- return typeof source === "string" ? source : source.src;
408
+ if (typeof source === "string") return source;
409
+ if (source && typeof source === "object") {
410
+ const value = source;
411
+ if (typeof value.src === "string") return value.src;
412
+ if (typeof value.default === "string") return value.default;
413
+ if (value.default && typeof value.default === "object" && value.default !== null) {
414
+ const nested = value.default;
415
+ if (typeof nested.src === "string") return nested.src;
416
+ }
417
+ }
418
+ throw new Error("RainBackground image must be a URL string.");
419
+ }
420
+ function toDisplayUrl(source, cache) {
421
+ const existing = cache.get(source);
422
+ if (existing) return existing;
423
+ if (!source.startsWith("data:")) return source;
424
+ const comma = source.indexOf(",");
425
+ if (comma === -1) return source;
426
+ const header = source.slice(0, comma);
427
+ const data = source.slice(comma + 1);
428
+ const mime = header.match(/^data:([^;,]+)/i)?.[1] ?? "image/png";
429
+ const binary = atob(data);
430
+ const bytes = new Uint8Array(binary.length);
431
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
432
+ const url = URL.createObjectURL(new Blob([bytes], { type: mime }));
433
+ cache.set(source, url);
434
+ return url;
409
435
  }
410
436
  var RainBackground = class {
411
437
  element;
438
+ objectUrls = [];
412
439
  constructor(options = {}) {
413
440
  const {
414
441
  images = [...BUILTIN_RAIN_IMAGES],
415
442
  density = 28,
416
443
  minSize = 28,
417
- maxSize = 28,
444
+ maxSize = 72,
418
445
  minDuration = 8,
419
446
  maxDuration = 18,
420
447
  target = document.body,
@@ -427,8 +454,11 @@ var RainBackground = class {
427
454
  this.element.className = "helaui-rain-background";
428
455
  this.element.setAttribute("aria-hidden", "true");
429
456
  this.element.style.zIndex = String(zIndex);
457
+ const urlCache = /* @__PURE__ */ new Map();
458
+ const resolved = images.map((image) => toDisplayUrl(toImageUrl(image), urlCache));
459
+ this.objectUrls.push(...new Set(urlCache.values()));
430
460
  for (let index = 0; index < density; index += 1) {
431
- this.element.appendChild(this.createDrop(images, minSize, maxSize, minDuration, maxDuration));
461
+ this.element.appendChild(this.createDrop(resolved, minSize, maxSize, minDuration, maxDuration));
432
462
  }
433
463
  target.prepend(this.element);
434
464
  }
@@ -436,9 +466,16 @@ var RainBackground = class {
436
466
  const drop = document.createElement("div");
437
467
  const duration = randomBetween(minDuration, maxDuration);
438
468
  const size = randomBetween(minSize, maxSize);
439
- const src = toImageUrl(pick(images));
469
+ const src = pick(images);
470
+ const img = document.createElement("img");
471
+ img.className = "helaui-rain-background__image";
472
+ img.src = src;
473
+ img.alt = "";
474
+ img.draggable = false;
475
+ img.decoding = "async";
476
+ img.loading = "eager";
477
+ img.setAttribute("fetchpriority", "high");
440
478
  drop.className = "helaui-rain-background__drop";
441
- drop.style.backgroundImage = `url(${JSON.stringify(src)})`;
442
479
  drop.style.setProperty("--helaui-rain-x", `${randomBetween(0, 100)}%`);
443
480
  drop.style.setProperty("--helaui-rain-size", `${size}px`);
444
481
  drop.style.setProperty("--helaui-rain-duration", `${duration.toFixed(2)}s`);
@@ -446,11 +483,14 @@ var RainBackground = class {
446
483
  drop.style.setProperty("--helaui-rain-rotation", `${randomBetween(-35, 35).toFixed(1)}deg`);
447
484
  drop.style.setProperty("--helaui-rain-spin", `${randomBetween(90, 360).toFixed(0)}deg`);
448
485
  drop.style.setProperty("--helaui-rain-drift", `${randomBetween(-40, 40).toFixed(0)}px`);
486
+ drop.appendChild(img);
449
487
  return drop;
450
488
  }
451
489
  /** Removes the background from the DOM. */
452
490
  destroy() {
453
491
  this.element.remove();
492
+ for (const url of this.objectUrls) URL.revokeObjectURL(url);
493
+ this.objectUrls.length = 0;
454
494
  }
455
495
  };
456
496
  var DEFAULT_RAIN_IMAGES = ["fritter.png", "swirl.png", "kokis.png", "leaf.png"];
package/dist/index.d.cts CHANGED
@@ -162,6 +162,7 @@ interface RainBackgroundOptions {
162
162
  /** Full-screen animated background with falling image particles. */
163
163
  declare class RainBackground {
164
164
  readonly element: HTMLDivElement;
165
+ private readonly objectUrls;
165
166
  constructor(options?: RainBackgroundOptions);
166
167
  private createDrop;
167
168
  /** Removes the background from the DOM. */
package/dist/index.d.ts CHANGED
@@ -162,6 +162,7 @@ interface RainBackgroundOptions {
162
162
  /** Full-screen animated background with falling image particles. */
163
163
  declare class RainBackground {
164
164
  readonly element: HTMLDivElement;
165
+ private readonly objectUrls;
165
166
  constructor(options?: RainBackgroundOptions);
166
167
  private createDrop;
167
168
  /** Removes the background from the DOM. */
package/dist/index.js CHANGED
@@ -352,16 +352,43 @@ function pick(items) {
352
352
  return items[Math.floor(Math.random() * items.length)];
353
353
  }
354
354
  function toImageUrl(source) {
355
- return typeof source === "string" ? source : source.src;
355
+ if (typeof source === "string") return source;
356
+ if (source && typeof source === "object") {
357
+ const value = source;
358
+ if (typeof value.src === "string") return value.src;
359
+ if (typeof value.default === "string") return value.default;
360
+ if (value.default && typeof value.default === "object" && value.default !== null) {
361
+ const nested = value.default;
362
+ if (typeof nested.src === "string") return nested.src;
363
+ }
364
+ }
365
+ throw new Error("RainBackground image must be a URL string.");
366
+ }
367
+ function toDisplayUrl(source, cache) {
368
+ const existing = cache.get(source);
369
+ if (existing) return existing;
370
+ if (!source.startsWith("data:")) return source;
371
+ const comma = source.indexOf(",");
372
+ if (comma === -1) return source;
373
+ const header = source.slice(0, comma);
374
+ const data = source.slice(comma + 1);
375
+ const mime = header.match(/^data:([^;,]+)/i)?.[1] ?? "image/png";
376
+ const binary = atob(data);
377
+ const bytes = new Uint8Array(binary.length);
378
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
379
+ const url = URL.createObjectURL(new Blob([bytes], { type: mime }));
380
+ cache.set(source, url);
381
+ return url;
356
382
  }
357
383
  var RainBackground = class {
358
384
  element;
385
+ objectUrls = [];
359
386
  constructor(options = {}) {
360
387
  const {
361
388
  images = [...BUILTIN_RAIN_IMAGES],
362
389
  density = 28,
363
390
  minSize = 28,
364
- maxSize = 28,
391
+ maxSize = 72,
365
392
  minDuration = 8,
366
393
  maxDuration = 18,
367
394
  target = document.body,
@@ -374,8 +401,11 @@ var RainBackground = class {
374
401
  this.element.className = "helaui-rain-background";
375
402
  this.element.setAttribute("aria-hidden", "true");
376
403
  this.element.style.zIndex = String(zIndex);
404
+ const urlCache = /* @__PURE__ */ new Map();
405
+ const resolved = images.map((image) => toDisplayUrl(toImageUrl(image), urlCache));
406
+ this.objectUrls.push(...new Set(urlCache.values()));
377
407
  for (let index = 0; index < density; index += 1) {
378
- this.element.appendChild(this.createDrop(images, minSize, maxSize, minDuration, maxDuration));
408
+ this.element.appendChild(this.createDrop(resolved, minSize, maxSize, minDuration, maxDuration));
379
409
  }
380
410
  target.prepend(this.element);
381
411
  }
@@ -383,9 +413,16 @@ var RainBackground = class {
383
413
  const drop = document.createElement("div");
384
414
  const duration = randomBetween(minDuration, maxDuration);
385
415
  const size = randomBetween(minSize, maxSize);
386
- const src = toImageUrl(pick(images));
416
+ const src = pick(images);
417
+ const img = document.createElement("img");
418
+ img.className = "helaui-rain-background__image";
419
+ img.src = src;
420
+ img.alt = "";
421
+ img.draggable = false;
422
+ img.decoding = "async";
423
+ img.loading = "eager";
424
+ img.setAttribute("fetchpriority", "high");
387
425
  drop.className = "helaui-rain-background__drop";
388
- drop.style.backgroundImage = `url(${JSON.stringify(src)})`;
389
426
  drop.style.setProperty("--helaui-rain-x", `${randomBetween(0, 100)}%`);
390
427
  drop.style.setProperty("--helaui-rain-size", `${size}px`);
391
428
  drop.style.setProperty("--helaui-rain-duration", `${duration.toFixed(2)}s`);
@@ -393,11 +430,14 @@ var RainBackground = class {
393
430
  drop.style.setProperty("--helaui-rain-rotation", `${randomBetween(-35, 35).toFixed(1)}deg`);
394
431
  drop.style.setProperty("--helaui-rain-spin", `${randomBetween(90, 360).toFixed(0)}deg`);
395
432
  drop.style.setProperty("--helaui-rain-drift", `${randomBetween(-40, 40).toFixed(0)}px`);
433
+ drop.appendChild(img);
396
434
  return drop;
397
435
  }
398
436
  /** Removes the background from the DOM. */
399
437
  destroy() {
400
438
  this.element.remove();
439
+ for (const url of this.objectUrls) URL.revokeObjectURL(url);
440
+ this.objectUrls.length = 0;
401
441
  }
402
442
  };
403
443
  var DEFAULT_RAIN_IMAGES = ["fritter.png", "swirl.png", "kokis.png", "leaf.png"];
@@ -17,9 +17,14 @@
17
17
  animation: helaui-rain-fall var(--helaui-rain-duration, 10s) linear infinite;
18
18
  animation-delay: var(--helaui-rain-delay, 0s);
19
19
  transform: rotate(var(--helaui-rain-rotation, 0deg));
20
- background-size: contain;
21
- background-repeat: no-repeat;
22
- background-position: center;
20
+ }
21
+
22
+ .helaui-rain-background__image {
23
+ display: block;
24
+ width: 100%;
25
+ height: 100%;
26
+ object-fit: contain;
27
+ pointer-events: none;
23
28
  }
24
29
 
25
30
  @keyframes helaui-rain-fall {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mccustomapps/helaui",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "HelaUI components with Sinhala font support",
5
5
  "author": "mccustomapps",
6
6
  "license": "MIT",