@inlaylab/opaline-element 1.0.0 → 1.1.1

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
@@ -20,10 +20,19 @@ npm i @inlaylab/opaline-element
20
20
  ></opaline-avatar>
21
21
  ```
22
22
 
23
- Attributes: `seed`, `look` (`morph`, `gradient`, `aura` or `clay`), `size`, `src`, `alt`, `palette`, `finish`, `expression`, `mood`, `face`, `family`, `backdrop`, `shape`, `body` and `ears`.
23
+ Attributes: `seed`, `look` (`morph`, `gradient`, `aura`, `clay` or `initials`), `size`, `src`, `alt`, `palette`, `finish`, `expression`, `mood`, `face`, `family`, `backdrop`, `shape`, `body`, `ears` and `text`.
24
24
 
25
25
  With `src`, the photo shows once it loads, and a failed photo leaves the generated avatar. With `alt`, the avatar has `role="img"`; without it, it is hidden from assistive tech.
26
26
 
27
+ Groups: wrap avatars in `<opaline-avatar-group>`. It overlaps them and shows a `+N` chip past `max`. Its `look`, `size`, `palette`, `shape` and `finish` are defaults for every child that does not set its own.
28
+
29
+ ```html
30
+ <opaline-avatar-group look="clay" size="40" max="4" alt="Project members">
31
+ <opaline-avatar seed="ada@inlay.dev" src="/ada.jpg" alt="Ada"></opaline-avatar>
32
+ <opaline-avatar seed="grace@inlay.dev"></opaline-avatar>
33
+ </opaline-avatar-group>
34
+ ```
35
+
27
36
  Same seed, same avatar: from 1.0, the same seed and options give a byte-identical SVG in every 1.x release.
28
37
 
29
38
  [Playground and docs](https://inlaylab.github.io/Opaline/) · [GitHub](https://github.com/Inlaylab/Opaline) · MIT for code and art.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,19 @@
1
+ /**
2
+ * `<opaline-avatar-group max="3" look="clay">` around `<opaline-avatar>` children: overlaps them,
3
+ * shows at most `max`, then a `+N` chip. Children added or removed later are laid out again.
4
+ */
5
+ export declare class OpalineAvatarGroup extends HTMLElement {
6
+ #private;
7
+ static observedAttributes: readonly string[];
8
+ connectedCallback(): void;
9
+ disconnectedCallback(): void;
10
+ attributeChangedCallback(): void;
11
+ }
12
+ declare global {
13
+ interface HTMLElementTagNameMap {
14
+ 'opaline-avatar-group': OpalineAvatarGroup;
15
+ }
16
+ }
1
17
  /** `<opaline-avatar seed="ada@inlay.dev" look="morph">`: a seeded avatar with an optional photo. */
2
18
  export declare class OpalineAvatar extends HTMLElement {
3
19
  #private;
package/dist/index.js CHANGED
@@ -1,14 +1,93 @@
1
- import { createAvatar, escapeXml } from "@inlaylab/opaline";
1
+ import { avatarGroup, createAvatar, escapeXml } from "@inlaylab/opaline";
2
2
  import { aura } from "@inlaylab/opaline/aura";
3
3
  import { clay } from "@inlaylab/opaline/clay";
4
4
  import { gradient } from "@inlaylab/opaline/gradient";
5
+ import { initials } from "@inlaylab/opaline/initials";
5
6
  import { morph } from "@inlaylab/opaline/morph";
7
+ // src/group.ts
8
+ /** Group attributes that set a default on every child avatar that does not set its own. */
9
+ const SHARED = [
10
+ "look",
11
+ "size",
12
+ "palette",
13
+ "shape",
14
+ "finish"
15
+ ];
16
+ /**
17
+ * `<opaline-avatar-group max="3" look="clay">` around `<opaline-avatar>` children: overlaps them,
18
+ * shows at most `max`, then a `+N` chip. Children added or removed later are laid out again.
19
+ */
20
+ var OpalineAvatarGroup = class extends HTMLElement {
21
+ static observedAttributes = [
22
+ "max",
23
+ "alt",
24
+ ...SHARED
25
+ ];
26
+ #observer = new MutationObserver(() => this.#layout());
27
+ #given = /* @__PURE__ */ new WeakMap();
28
+ #more;
29
+ connectedCallback() {
30
+ this.#layout();
31
+ this.#observer.observe(this, { childList: true });
32
+ }
33
+ disconnectedCallback() {
34
+ this.#observer.disconnect();
35
+ }
36
+ attributeChangedCallback() {
37
+ if (this.isConnected) this.#layout();
38
+ }
39
+ #layout() {
40
+ const get = (name) => this.getAttribute(name);
41
+ const n = Number(get("size"));
42
+ const size = Number.isFinite(n) && n > 0 ? n : 40;
43
+ const kids = [...this.children].filter((e) => e.localName === "opaline-avatar");
44
+ const g = avatarGroup(kids.map(() => 0), {
45
+ max: Number(get("max") ?? NaN),
46
+ size,
47
+ shape: get("shape") ?? void 0
48
+ });
49
+ kids.forEach((kid, i) => {
50
+ let given = this.#given.get(kid);
51
+ if (!given) this.#given.set(kid, given = /* @__PURE__ */ new Set());
52
+ for (const name of SHARED) {
53
+ if (kid.hasAttribute(name) && !given.has(name)) continue;
54
+ const v = name === "size" ? String(size) : get(name);
55
+ if (v === null) {
56
+ if (given.delete(name)) kid.removeAttribute(name);
57
+ } else if (kid.getAttribute(name) !== v) {
58
+ kid.setAttribute(name, v);
59
+ given.add(name);
60
+ } else given.add(name);
61
+ }
62
+ kid.toggleAttribute("data-opaline-member", i < g.shown.length);
63
+ kid.style.cssText = i >= g.shown.length ? "display:none" : i ? g.css.member : g.css.first;
64
+ });
65
+ this.setAttribute("data-opaline-group", "");
66
+ this.setAttribute("role", "group");
67
+ const alt = get("alt");
68
+ if (alt) this.setAttribute("aria-label", alt);
69
+ else this.removeAttribute("aria-label");
70
+ this.style.cssText = g.css.root;
71
+ if (g.rest) {
72
+ const more = this.#more ??= document.createElement("span");
73
+ more.setAttribute("data-opaline-more", "");
74
+ more.setAttribute("role", "img");
75
+ more.setAttribute("aria-label", `${g.rest} more`);
76
+ more.style.cssText = g.css.more;
77
+ more.textContent = `+${g.rest}`;
78
+ if (this.lastElementChild !== more) this.append(more);
79
+ } else this.#more?.remove();
80
+ this.#observer.takeRecords();
81
+ }
82
+ };
83
+ if (!customElements.get("opaline-avatar-group")) customElements.define("opaline-avatar-group", OpalineAvatarGroup);
6
84
  // src/index.ts
7
85
  const LOOKS = {
8
86
  morph,
9
87
  gradient,
10
88
  aura,
11
- clay
89
+ clay,
90
+ initials
12
91
  };
13
92
  const ATTRS = [
14
93
  "seed",
@@ -25,7 +104,8 @@ const ATTRS = [
25
104
  "shape",
26
105
  "finish",
27
106
  "body",
28
- "ears"
107
+ "ears",
108
+ "text"
29
109
  ];
30
110
  const IMG_STYLE = "position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit";
31
111
  let count = 0;
@@ -63,7 +143,8 @@ var OpalineAvatar = class extends HTMLElement {
63
143
  shape: get("shape"),
64
144
  finish: get("finish"),
65
145
  body: get("body"),
66
- ears: get("ears")
146
+ ears: get("ears"),
147
+ text: get("text")
67
148
  }).svg;
68
149
  const a11y = alt ? `role="img" aria-label="${escapeXml(alt)}"` : "aria-hidden=\"true\"";
69
150
  const img = src ? `<img src="${escapeXml(src)}" alt="" style="${IMG_STYLE}" hidden>` : "";
@@ -76,4 +157,4 @@ var OpalineAvatar = class extends HTMLElement {
76
157
  }
77
158
  };
78
159
  if (!customElements.get("opaline-avatar")) customElements.define("opaline-avatar", OpalineAvatar);
79
- export { OpalineAvatar };
160
+ export { OpalineAvatar, OpalineAvatarGroup };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inlaylab/opaline-element",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "<opaline-avatar>: seeded avatars as a framework-free custom element.",
5
5
  "keywords": [
6
6
  "avatar",
@@ -39,7 +39,7 @@
39
39
  "dist"
40
40
  ],
41
41
  "dependencies": {
42
- "@inlaylab/opaline": "1.0.0"
42
+ "@inlaylab/opaline": "1.1.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@inlaylab/opaline-contract": "0.0.0",