@rogieking/figui3 10.0.1 → 10.0.3

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
@@ -13126,6 +13126,7 @@ const FIG_MEDIA_FORWARDED_EVENTS = [
13126
13126
  * @attr {string} src - Media source URL
13127
13127
  * @attr {string} type - "image" (default) or "video" (for fig-media)
13128
13128
  * @attr {string} alt - Alt text for the generated image (default "")
13129
+ * @attr {boolean} loaded - Read-only reflected state when an image has loaded successfully
13129
13130
  * @attr {boolean} upload - Show upload overlay (generates fig-input-file)
13130
13131
  * @attr {boolean} loading-indicator - Set to `false` to disable the generated image loading spinner
13131
13132
  * @attr {string} label - Upload button label (default "Upload")
@@ -13344,6 +13345,7 @@ class FigMedia extends HTMLElement {
13344
13345
  }
13345
13346
 
13346
13347
  #syncImageLoadingState() {
13348
+ this.#syncImageLoadedState();
13347
13349
  const src = this.#currentMediaSrc();
13348
13350
  if (!src || !this.#usesDefaultImageLoadingIndicator()) {
13349
13351
  this.#finishImageLoading();
@@ -13383,6 +13385,18 @@ class FigMedia extends HTMLElement {
13383
13385
  }, 150);
13384
13386
  }
13385
13387
 
13388
+ #syncImageLoadedState() {
13389
+ const image = this.#mediaEl?.tagName === "IMG" ? this.#mediaEl : null;
13390
+ const src =
13391
+ image?.currentSrc ||
13392
+ image?.getAttribute("src") ||
13393
+ this.#currentMediaSrc();
13394
+ this.toggleAttribute(
13395
+ "loaded",
13396
+ Boolean(src && image?.complete && image.naturalWidth > 0),
13397
+ );
13398
+ }
13399
+
13386
13400
  #finishImageLoading() {
13387
13401
  if (this.#loadingTimer !== null) {
13388
13402
  clearTimeout(this.#loadingTimer);
@@ -13553,6 +13567,7 @@ class FigMedia extends HTMLElement {
13553
13567
  if (!this.#mediaEl.hasAttribute("data-generated")) return;
13554
13568
  const src = this.#currentMediaSrc();
13555
13569
  if (this.#mediaEl.getAttribute("src") !== src) {
13570
+ this.removeAttribute("loaded");
13556
13571
  if (src) {
13557
13572
  this.#mediaEl.setAttribute("src", src);
13558
13573
  } else {
@@ -13880,6 +13895,7 @@ class FigMedia extends HTMLElement {
13880
13895
  media.tagName === "IMG" &&
13881
13896
  (event.type === "load" || event.type === "error")
13882
13897
  ) {
13898
+ this.toggleAttribute("loaded", event.type === "load");
13883
13899
  this.#finishImageLoading();
13884
13900
  }
13885
13901
  const detail = {
@@ -14057,9 +14073,11 @@ figDefineElement("fig-video", FigVideo);
14057
14073
  * @attr {boolean} disabled - Dim + non-interactive
14058
14074
  * @attr {boolean} full - Stretch to available width (CSS; default width is already 100%)
14059
14075
  * @attr {string} size - Set to `large` for spacer-2 card padding and spacer-1 label spacing
14076
+ * @attr {string} direction - `vertical` (default) or `horizontal` list layout
14060
14077
  * @attr {string} aspect-ratio - Forwarded to fig-image (default `1/1`)
14061
14078
  * @attr {string} fit - Forwarded to fig-image (default `contain`)
14062
14079
  * @attr {string} label-line-clamp - `1` (default) or `2`
14080
+ * @attr {string} label-overflow - `ellipsis` (default) or horizontal `fade`
14063
14081
  */
14064
14082
  class FigCard extends HTMLElement {
14065
14083
  static get observedAttributes() {
@@ -14073,9 +14091,11 @@ class FigCard extends HTMLElement {
14073
14091
  "disabled",
14074
14092
  "full",
14075
14093
  "size",
14094
+ "direction",
14076
14095
  "aspect-ratio",
14077
14096
  "fit",
14078
14097
  "label-line-clamp",
14098
+ "label-overflow",
14079
14099
  ];
14080
14100
  }
14081
14101
 
@@ -14273,6 +14293,16 @@ class FigCard extends HTMLElement {
14273
14293
  this.appendChild(this.#footerEl);
14274
14294
  }
14275
14295
 
14296
+ #syncLabelOverflow() {
14297
+ const fade = this.getAttribute("label-overflow") === "fade";
14298
+ this.querySelectorAll(
14299
+ ":scope > fig-footer > :is(.fig-card-label, .fig-card-sublabel)",
14300
+ ).forEach((element) => {
14301
+ element.classList.toggle("fig-overflow-fade", fade);
14302
+ element.classList.toggle("fig-overflow-fade-horizontal", fade);
14303
+ });
14304
+ }
14305
+
14276
14306
  #sync() {
14277
14307
  if (!this.#usesGeneratedMedia()) {
14278
14308
  this.#syncManual();
@@ -14329,6 +14359,7 @@ class FigCard extends HTMLElement {
14329
14359
  if (this.#footerEl) {
14330
14360
  this.#footerEl.hidden = !label && !sublabel;
14331
14361
  }
14362
+ this.#syncLabelOverflow();
14332
14363
  }
14333
14364
 
14334
14365
  #syncManual() {
@@ -14352,6 +14383,7 @@ class FigCard extends HTMLElement {
14352
14383
  this.#labelEl.hidden = !label;
14353
14384
  }
14354
14385
  if (this.#sublabelEl) this.#sublabelEl.textContent = sublabel;
14386
+ this.#syncLabelOverflow();
14355
14387
  }
14356
14388
  }
14357
14389
  figDefineElement("fig-card", FigCard);
@@ -19772,6 +19804,8 @@ figDefineElement("fig-color-tip", FigColorTip);
19772
19804
  * @attr {boolean} disabled - Whether this choice is disabled
19773
19805
  */
19774
19806
  class FigChoice extends HTMLElement {
19807
+ #childObserver = null;
19808
+
19775
19809
  static get observedAttributes() {
19776
19810
  return ["selected", "disabled"];
19777
19811
  }
@@ -19781,21 +19815,34 @@ class FigChoice extends HTMLElement {
19781
19815
  if (!this.hasAttribute("tabindex")) {
19782
19816
  this.setAttribute("tabindex", "0");
19783
19817
  }
19784
- this.setAttribute(
19785
- "aria-selected",
19786
- figBooleanAttribute(this, "selected") ? "true" : "false",
19787
- );
19818
+ this.#syncSelectedState();
19788
19819
  if (figBooleanAttribute(this, "disabled")) {
19789
19820
  this.setAttribute("aria-disabled", "true");
19790
19821
  }
19791
19822
  }
19792
19823
 
19824
+ disconnectedCallback() {
19825
+ this.#childObserver?.disconnect();
19826
+ }
19827
+
19828
+ #syncSelectedState() {
19829
+ const selected = figBooleanAttribute(this, "selected");
19830
+ this.setAttribute("aria-selected", selected ? "true" : "false");
19831
+ for (const child of this.children) {
19832
+ child.toggleAttribute("selected", selected);
19833
+ }
19834
+ this.#childObserver?.disconnect();
19835
+ if (selected && this.isConnected) {
19836
+ this.#childObserver ??= new MutationObserver(() =>
19837
+ this.#syncSelectedState(),
19838
+ );
19839
+ this.#childObserver.observe(this, { childList: true });
19840
+ }
19841
+ }
19842
+
19793
19843
  attributeChangedCallback(name) {
19794
19844
  if (name === "selected") {
19795
- this.setAttribute(
19796
- "aria-selected",
19797
- figBooleanAttribute(this, "selected") ? "true" : "false",
19798
- );
19845
+ this.#syncSelectedState();
19799
19846
  }
19800
19847
  if (name === "disabled") {
19801
19848
  const isDisabled =
@@ -19898,19 +19945,11 @@ class FigChooser extends HTMLElement {
19898
19945
  const raw = this.getAttribute("columns");
19899
19946
  const columns = raw === null ? NaN : Number(raw);
19900
19947
  const validColumns = Number.isInteger(columns) && columns > 0;
19901
- const effectiveColumns = validColumns ? columns : 2;
19902
19948
  if (validColumns) {
19903
- this.style.setProperty("--fig-chooser-grid-columns", String(effectiveColumns));
19949
+ this.style.setProperty("--fig-chooser-grid-columns", String(columns));
19904
19950
  } else {
19905
19951
  this.style.removeProperty("--fig-chooser-grid-columns");
19906
19952
  }
19907
- const isGrid = this.getAttribute("layout") === "grid";
19908
- this.choices.forEach((choice, index) => {
19909
- choice.toggleAttribute(
19910
- "data-fig-chooser-first-row",
19911
- isGrid && index < effectiveColumns,
19912
- );
19913
- });
19914
19953
  }
19915
19954
 
19916
19955
  get choices() {
@@ -20160,7 +20199,8 @@ class FigChooser extends HTMLElement {
20160
20199
  !c.hasAttribute("disabled") || c.getAttribute("disabled") === "false",
20161
20200
  );
20162
20201
  if (!choices.length) return;
20163
- const currentIndex = choices.indexOf(this.#selectedChoice);
20202
+ const focusedChoice = this.#findChoiceFromTarget(event.target);
20203
+ const currentIndex = choices.indexOf(focusedChoice);
20164
20204
  let nextIndex = currentIndex;
20165
20205
 
20166
20206
  const loop = this.hasAttribute("loop");
@@ -20195,12 +20235,9 @@ class FigChooser extends HTMLElement {
20195
20235
  case "Enter":
20196
20236
  case " ":
20197
20237
  event.preventDefault();
20198
- if (document.activeElement?.matches(this.#choiceSelector)) {
20199
- const focused = this.#findChoiceFromTarget(document.activeElement);
20200
- if (focused && focused !== this.#selectedChoice) {
20201
- this.selectedChoice = focused;
20202
- this.#emitEvents();
20203
- }
20238
+ if (focusedChoice && focusedChoice !== this.#selectedChoice) {
20239
+ this.selectedChoice = focusedChoice;
20240
+ this.#emitEvents();
20204
20241
  }
20205
20242
  return;
20206
20243
  default:
@@ -20208,9 +20245,9 @@ class FigChooser extends HTMLElement {
20208
20245
  }
20209
20246
 
20210
20247
  if (nextIndex !== currentIndex && choices[nextIndex]) {
20211
- this.selectedChoice = choices[nextIndex];
20212
- choices[nextIndex].focus({ preventScroll: !this.autoScroll });
20213
- this.#emitEvents();
20248
+ const nextChoice = choices[nextIndex];
20249
+ nextChoice.focus({ preventScroll: true });
20250
+ if (this.autoScroll) this.#scrollToChoice(nextChoice);
20214
20251
  }
20215
20252
  }
20216
20253
 
@@ -20381,12 +20418,14 @@ class FigChooser extends HTMLElement {
20381
20418
  }
20382
20419
 
20383
20420
  #applyOverflowMode() {
20384
- if (this.#overflowMode === "scrollbar") {
20421
+ const usesScrollbar = this.#overflowMode === "scrollbar";
20422
+ const isGrid = this.getAttribute("layout") === "grid";
20423
+ if (usesScrollbar || isGrid) {
20385
20424
  this.#removeNavButtons();
20386
- this.classList.add("fig-overflow-fade");
20425
+ this.classList.toggle("fig-overflow-fade", usesScrollbar);
20387
20426
  this.classList.toggle(
20388
20427
  "fig-overflow-fade-horizontal",
20389
- this.getAttribute("layout") === "horizontal",
20428
+ usesScrollbar && this.getAttribute("layout") === "horizontal",
20390
20429
  );
20391
20430
  } else {
20392
20431
  this.#createNavButtons();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "10.0.1",
3
+ "version": "10.0.3",
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": "SEE LICENSE IN LICENSE",