@rogieking/figui3 6.19.1 → 6.20.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/fig.js CHANGED
@@ -11906,6 +11906,279 @@ class FigVideo extends FigMedia {
11906
11906
  }
11907
11907
  customElements.define("fig-video", FigVideo);
11908
11908
 
11909
+ /**
11910
+ * <fig-card> — Media card with optional link, selection chrome, and truncated label.
11911
+ *
11912
+ * Composes a generated `fig-image` (or authored fig-image/fig-media/fig-preview).
11913
+ * Selection is attribute-only (`selected`); apps own toggle/group logic.
11914
+ * When `href` is set (and not disabled), content wraps in a real `<a>`.
11915
+ *
11916
+ * @attr {string} src - Image URL forwarded to generated fig-image
11917
+ * @attr {string} alt - Alt text forwarded to generated fig-image
11918
+ * @attr {string} label - Card title text (preferred over `text`)
11919
+ * @attr {string} text - Alias for `label`
11920
+ * @attr {string} sublabel - Secondary one-line text under the label
11921
+ * @attr {string} href - Makes the card a link
11922
+ * @attr {string} target - Forwarded to the `<a>`
11923
+ * @attr {boolean} selected - Selected chrome (CSS only)
11924
+ * @attr {boolean} disabled - Dim + non-interactive; no `<a>` when disabled
11925
+ * @attr {boolean} full - Stretch to available width (CSS; default width is already 100%)
11926
+ * @attr {string} aspect-ratio - Forwarded to fig-image (default `1/1`)
11927
+ * @attr {string} fit - Forwarded to fig-image (default `contain`)
11928
+ * @attr {string} label-line-clamp - `1` (default) or `2`
11929
+ */
11930
+ class FigCard extends HTMLElement {
11931
+ static get observedAttributes() {
11932
+ return [
11933
+ "src",
11934
+ "alt",
11935
+ "label",
11936
+ "text",
11937
+ "sublabel",
11938
+ "href",
11939
+ "target",
11940
+ "selected",
11941
+ "disabled",
11942
+ "full",
11943
+ "aspect-ratio",
11944
+ "fit",
11945
+ "label-line-clamp",
11946
+ ];
11947
+ }
11948
+
11949
+ #linkEl = null;
11950
+ #mediaWrap = null;
11951
+ #textWrap = null;
11952
+ #imageEl = null;
11953
+ #labelEl = null;
11954
+ #sublabelEl = null;
11955
+ #usesAuthoredMedia = false;
11956
+
11957
+ connectedCallback() {
11958
+ this.#ensureStructure();
11959
+ this.#sync();
11960
+ }
11961
+
11962
+ attributeChangedCallback() {
11963
+ if (!this.isConnected) return;
11964
+ this.#ensureStructure();
11965
+ this.#sync();
11966
+ }
11967
+
11968
+ #labelText() {
11969
+ return this.getAttribute("label") ?? this.getAttribute("text") ?? "";
11970
+ }
11971
+
11972
+ #sublabelText() {
11973
+ return this.getAttribute("sublabel") ?? "";
11974
+ }
11975
+
11976
+ #wantsLink() {
11977
+ const href = this.getAttribute("href");
11978
+ return Boolean(href) && !figBooleanAttribute(this, "disabled");
11979
+ }
11980
+
11981
+ #lineClamp() {
11982
+ const raw = this.getAttribute("label-line-clamp");
11983
+ return raw === "2" ? "2" : "1";
11984
+ }
11985
+
11986
+ #findAuthoredMedia() {
11987
+ return this.querySelector(
11988
+ ":scope > fig-image:not([data-generated]), :scope > fig-media:not([data-generated]), :scope > fig-preview:not([data-generated]), :scope > .fig-card-link > .fig-card-media > fig-image:not([data-generated]), :scope > .fig-card-link > .fig-card-media > fig-media:not([data-generated]), :scope > .fig-card-link > .fig-card-media > fig-preview:not([data-generated])",
11989
+ );
11990
+ }
11991
+
11992
+ #ensureStructure() {
11993
+ const authored = this.#findAuthoredMedia();
11994
+ this.#usesAuthoredMedia = Boolean(authored);
11995
+
11996
+ const wantAnchor = this.#wantsLink();
11997
+ const tag = wantAnchor ? "a" : "div";
11998
+
11999
+ if (!this.#linkEl || this.#linkEl.tagName.toLowerCase() !== tag) {
12000
+ const next = document.createElement(tag);
12001
+ next.className = "fig-card-link";
12002
+ if (this.#linkEl) {
12003
+ while (this.#linkEl.firstChild) {
12004
+ next.appendChild(this.#linkEl.firstChild);
12005
+ }
12006
+ this.#linkEl.replaceWith(next);
12007
+ } else {
12008
+ this.appendChild(next);
12009
+ }
12010
+ this.#linkEl = next;
12011
+ }
12012
+
12013
+ if (!this.#mediaWrap || !this.#mediaWrap.isConnected) {
12014
+ this.#mediaWrap = this.#linkEl.querySelector(":scope > .fig-card-media");
12015
+ if (!this.#mediaWrap) {
12016
+ this.#mediaWrap = document.createElement("div");
12017
+ this.#mediaWrap.className = "fig-card-media";
12018
+ this.#linkEl.prepend(this.#mediaWrap);
12019
+ }
12020
+ }
12021
+
12022
+ if (authored && authored.parentElement !== this.#mediaWrap) {
12023
+ this.#mediaWrap
12024
+ .querySelectorAll("[data-generated]")
12025
+ .forEach((el) => el.remove());
12026
+ this.#mediaWrap.appendChild(authored);
12027
+ this.#imageEl = null;
12028
+ }
12029
+
12030
+ if (!this.#usesAuthoredMedia) {
12031
+ this.#imageEl =
12032
+ this.#mediaWrap.querySelector(":scope > fig-image[data-generated]") ||
12033
+ null;
12034
+ if (!this.#imageEl) {
12035
+ this.#mediaWrap
12036
+ .querySelectorAll(
12037
+ ":scope > fig-image, :scope > fig-media, :scope > fig-preview",
12038
+ )
12039
+ .forEach((el) => {
12040
+ if (el.hasAttribute("data-generated")) el.remove();
12041
+ });
12042
+ this.#imageEl = document.createElement("fig-image");
12043
+ this.#imageEl.setAttribute("data-generated", "");
12044
+ this.#imageEl.setAttribute("full", "");
12045
+ this.#imageEl.setAttribute("size", "auto");
12046
+ this.#mediaWrap.appendChild(this.#imageEl);
12047
+ }
12048
+ }
12049
+
12050
+ if (!this.#textWrap || !this.#textWrap.isConnected) {
12051
+ this.#textWrap = this.#linkEl.querySelector(":scope > .fig-card-text");
12052
+ if (!this.#textWrap) {
12053
+ this.#textWrap = document.createElement("div");
12054
+ this.#textWrap.className = "fig-card-text";
12055
+ this.#textWrap.setAttribute("data-generated", "");
12056
+ this.#linkEl.appendChild(this.#textWrap);
12057
+ }
12058
+ }
12059
+
12060
+ if (!this.#labelEl || !this.#labelEl.isConnected) {
12061
+ this.#labelEl = this.#textWrap.querySelector(
12062
+ ":scope > .fig-card-label[data-generated]",
12063
+ );
12064
+ if (!this.#labelEl) {
12065
+ this.#labelEl =
12066
+ this.#linkEl.querySelector(":scope > .fig-card-label[data-generated]") ||
12067
+ null;
12068
+ if (!this.#labelEl) {
12069
+ this.#labelEl = document.createElement("label");
12070
+ this.#labelEl.className = "fig-card-label";
12071
+ this.#labelEl.setAttribute("data-generated", "");
12072
+ }
12073
+ this.#textWrap.appendChild(this.#labelEl);
12074
+ }
12075
+ }
12076
+
12077
+ if (!this.#sublabelEl || !this.#sublabelEl.isConnected) {
12078
+ this.#sublabelEl = this.#textWrap.querySelector(
12079
+ ":scope > .fig-card-sublabel[data-generated]",
12080
+ );
12081
+ if (!this.#sublabelEl) {
12082
+ this.#sublabelEl =
12083
+ this.#linkEl.querySelector(
12084
+ ":scope > .fig-card-sublabel[data-generated]",
12085
+ ) || null;
12086
+ if (!this.#sublabelEl) {
12087
+ this.#sublabelEl = document.createElement("span");
12088
+ this.#sublabelEl.className = "fig-card-sublabel";
12089
+ this.#sublabelEl.setAttribute("data-generated", "");
12090
+ }
12091
+ this.#labelEl.after(this.#sublabelEl);
12092
+ }
12093
+ }
12094
+ }
12095
+
12096
+ #sync() {
12097
+ if (!this.#linkEl) return;
12098
+
12099
+ const disabled = figBooleanAttribute(this, "disabled");
12100
+ const selected = figBooleanAttribute(this, "selected");
12101
+ const label = this.#labelText();
12102
+ const sublabel = this.#sublabelText();
12103
+ const clamp = this.#lineClamp();
12104
+
12105
+ this.style.setProperty("--fig-card-label-line-clamp", clamp);
12106
+
12107
+ if (this.#wantsLink()) {
12108
+ const href = this.getAttribute("href") || "";
12109
+ const target = this.getAttribute("target");
12110
+ this.#linkEl.setAttribute("href", href);
12111
+ if (target) {
12112
+ this.#linkEl.setAttribute("target", target);
12113
+ if (target === "_blank") {
12114
+ this.#linkEl.setAttribute("rel", "noopener noreferrer");
12115
+ } else {
12116
+ this.#linkEl.removeAttribute("rel");
12117
+ }
12118
+ } else {
12119
+ this.#linkEl.removeAttribute("target");
12120
+ this.#linkEl.removeAttribute("rel");
12121
+ }
12122
+ if (selected) {
12123
+ this.#linkEl.setAttribute("aria-current", "true");
12124
+ } else {
12125
+ this.#linkEl.removeAttribute("aria-current");
12126
+ }
12127
+ this.removeAttribute("role");
12128
+ this.removeAttribute("aria-label");
12129
+ } else {
12130
+ this.#linkEl.removeAttribute("href");
12131
+ this.#linkEl.removeAttribute("target");
12132
+ this.#linkEl.removeAttribute("rel");
12133
+ this.#linkEl.removeAttribute("aria-current");
12134
+ this.setAttribute("role", "group");
12135
+ const ariaLabel = [label, sublabel].filter(Boolean).join(", ");
12136
+ if (ariaLabel) {
12137
+ this.setAttribute("aria-label", ariaLabel);
12138
+ } else {
12139
+ this.removeAttribute("aria-label");
12140
+ }
12141
+ }
12142
+
12143
+ if (disabled) {
12144
+ this.setAttribute("aria-disabled", "true");
12145
+ } else {
12146
+ this.removeAttribute("aria-disabled");
12147
+ }
12148
+
12149
+ if (this.#imageEl && !this.#usesAuthoredMedia) {
12150
+ const src = this.getAttribute("src");
12151
+ const altAttr = this.getAttribute("alt");
12152
+ const aspect = this.getAttribute("aspect-ratio") || "1/1";
12153
+ const fit = this.getAttribute("fit") || "contain";
12154
+ const alt = altAttr != null ? altAttr : "";
12155
+
12156
+ if (src) this.#imageEl.setAttribute("src", src);
12157
+ else this.#imageEl.removeAttribute("src");
12158
+ this.#imageEl.setAttribute("alt", alt);
12159
+ this.#imageEl.setAttribute("aspect-ratio", aspect);
12160
+ this.#imageEl.setAttribute("fit", fit);
12161
+ this.#imageEl.setAttribute("full", "");
12162
+ this.#imageEl.setAttribute("size", "auto");
12163
+ }
12164
+
12165
+ if (this.#labelEl) {
12166
+ this.#labelEl.textContent = label;
12167
+ this.#labelEl.hidden = !label;
12168
+ }
12169
+
12170
+ if (this.#sublabelEl) {
12171
+ this.#sublabelEl.textContent = sublabel;
12172
+ this.#sublabelEl.hidden = !sublabel;
12173
+ }
12174
+
12175
+ if (this.#textWrap) {
12176
+ this.#textWrap.hidden = !label && !sublabel;
12177
+ }
12178
+ }
12179
+ }
12180
+ customElements.define("fig-card", FigCard);
12181
+
11909
12182
  /**
11910
12183
  * <fig-media-controls> — Standalone playback controls UI.
11911
12184
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "6.19.1",
3
+ "version": "6.20.0",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",